diff --git a/.bazelrc b/.bazelrc index 51cac5a..448576c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,9 +1,21 @@ -build --java_language_version=17 -build --tool_java_language_version=17 -build --java_runtime_version=remotejdk_17 -build --tool_java_runtime_version=remotejdk_17 - -test --test_output=errors +# .bazelrc +# From https://github.com/eclipse-score/bazel_registry common --registry=https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/ common --registry=https://bcr.bazel.build + +common --enable_bzlmod + +common --@score_baselibs//score/mw/log/flags:KRemote_Logging=False +common --@score_baselibs//score/json:base_library=nlohmann + + +## Coverage settings + +# With this instrumentation filter we ensure that `bazel coverage //src/...` is yielding the correct results +# The filter is that bazel instruments the path UNLESS the path contains /test/ or /tests/" +coverage --instrumentation_filter="//src(?!.*[/:](test|tests)[/:])" + +coverage --combined_report=lcov +coverage --coverage_report_generator=@bazel_tools//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:Main +coverage --test_env=COVERAGE_GCOV_OPTIONS=-bcu diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7cf9b0d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "eclipse-s-core", + "image": "ghcr.io/eclipse-score/devcontainer:latest" +} diff --git a/.github/actions/free_disk_space/action.yml b/.github/actions/free_disk_space/action.yml new file mode 100644 index 0000000..43af774 --- /dev/null +++ b/.github/actions/free_disk_space/action.yml @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: "Free Disk Space" +description: "Configures and calls endersonmenezes/free-disk-space to free disk space" + +branding: + icon: command + color: gray-dark + +runs: + using: composite + steps: + - name: Free Disk Space (Ubuntu) + uses: endersonmenezes/free-disk-space@v3 + with: + remove_android: true + remove_dotnet: true + remove_haskell: true + remove_tool_cache: true + remove_swap: true + rm_cmd: "rmz" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..317f179 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: Build & Test +on: + pull_request: + types: [opened, reopened, synchronize] + push: + branches: + - main + merge_group: + types: [checks_requested] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4.2.2 + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.18.0 + with: + # Avoid downloading Bazel every time. + bazelisk-cache: true + # Store build cache per workflow. + disk-cache: ${{ github.workflow }} + # Share repository cache between workflows. + repository-cache: true + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libpcap-dev + - name: Bazel build + run: | + bazel build //... + - name: Bazel test + run: | + bazel test //src/... diff --git a/.github/workflows/copyright.yml b/.github/workflows/copyright.yml index 08ef376..71afb29 100644 --- a/.github/workflows/copyright.yml +++ b/.github/workflows/copyright.yml @@ -12,11 +12,15 @@ # ******************************************************************************* name: Copyright checks -on: - pull_request: - types: [opened, reopened, synchronize] - merge_group: - types: [checks_requested] + +# TODO: Reenable after pitch +on: workflow_dispatch +# on: +# pull_request: +# types: [opened, reopened, synchronize] +# merge_group: +# types: [checks_requested] + jobs: copyright-check: uses: eclipse-score/cicd-workflows/.github/workflows/copyright.yml@main diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml new file mode 100644 index 0000000..f2fbd73 --- /dev/null +++ b/.github/workflows/coverage_report.yml @@ -0,0 +1,97 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# ******************************************************************************* + +name: Coverage Report + +on: + # Runs on every pull request regardless of path changes + pull_request: + # Allows manual triggering for testing without inputs + workflow_dispatch: + +jobs: + coverage-report: + runs-on: ubuntu-22.04 + permissions: + contents: read # Changed from write, as we no longer upload to Releases + + steps: + - name: Checkout Repository + uses: actions/checkout@v4.2.2 + + - name: Free Disk Space (Ubuntu) + uses: ./.github/actions/free_disk_space + + - name: Install lcov + run: | + sudo apt-get update + sudo apt-get install -y lcov + + - name: Setup Bazel with shared caching + uses: bazel-contrib/setup-bazel@0.15.0 + with: + bazelisk-cache: true + disk-cache: ${{ github.workflow }} + repository-cache: true + + - name: Run Unit Test with Coverage for C++ + run: | + # Note: Unit tests are not yet fully migrated to bazel from cmake + bazel coverage //src/... --build_tests_only + + - name: Collect Bazel Test Logs + if: always() + run: | + mkdir -p failed_test_logs + + # Check if logs directory exists to avoid script errors + if [ -d "bazel-testlogs" ]; then + echo "Collecting test.log and test.xml files..." + find bazel-testlogs/ -name 'test.log' -print0 | xargs -0 -I{} cp --parents {} failed_test_logs/ + else + echo "bazel-testlogs directory not found. Build might have failed before tests ran." + fi + shell: bash + + - name: Upload Test Logs Artifact + if: always() # RUNS EVEN IF TESTS FAIL + uses: actions/upload-artifact@v4 + with: + name: bazel-test-logs + path: failed_test_logs/ + retention-days: 2 + + + - name: Generate HTML Coverage Report + run: | + OUTPUT_PATH=$(bazel info output_path) + if [ -f "${OUTPUT_PATH}/_coverage/_coverage_report.dat" ]; then + genhtml "${OUTPUT_PATH}/_coverage/_coverage_report.dat" \ + -o=cpp_coverage \ + --show-details \ + --legend \ + --function-coverage \ + --branch-coverage + else + echo "Cannot run test coverage report generation as no coverage data file found." + fi + shell: bash + + # It will be available for download in the Actions "Summary" tab + # Note: upload-artifact automatically creates a zip file, so we don't need to zip manually + - name: Upload Coverage Artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}_coverage_report + path: cpp_coverage/ diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml index cfa4ae2..7be2fc2 100644 --- a/.github/workflows/docs-cleanup.yml +++ b/.github/workflows/docs-cleanup.yml @@ -18,9 +18,11 @@ permissions: pages: write id-token: write -on: - schedule: - - cron: '0 0 * * *' # Runs every day at midnight UTC +# TODO: Reenable after pitch +on: workflow_dispatch +# on: +# schedule: +# - cron: '0 0 * * *' # Runs every day at midnight UTC jobs: docs-cleanup: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 24bd399..d0253fe 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,14 +19,16 @@ permissions: pull-requests: write id-token: write -on: - pull_request_target: - types: [opened, reopened, synchronize] # Allows forks to trigger the docs build - push: - branches: - - main - merge_group: - types: [checks_requested] +# TODO: Reenable after pitch +# on: +on: workflow_dispatch +# pull_request_target: +# types: [opened, reopened, synchronize] # Allows forks to trigger the docs build +# push: +# branches: +# - main +# merge_group: +# types: [checks_requested] jobs: build-docs: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 620d697..26775c9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -13,11 +13,13 @@ name: Formatting checks -on: - pull_request: - types: [opened, reopened, synchronize] - merge_group: - types: [checks_requested] +# TODO: Reenable after pitch +on: workflow_dispatch +# on: +# pull_request: +# types: [opened, reopened, synchronize] +# merge_group: +# types: [checks_requested] jobs: formatting-check: diff --git a/BUILD b/BUILD index 473b5d5..401db96 100644 --- a/BUILD +++ b/BUILD @@ -15,11 +15,6 @@ load("@score_docs_as_code//:docs.bzl", "docs") load("@score_tooling//:defs.bzl", "copyright_checker", "dash_license_checker", "setup_starpls", "use_format_targets") load("//:project_config.bzl", "PROJECT_CONFIG") -setup_starpls( - name = "starpls_server", - visibility = ["//visibility:public"], -) - copyright_checker( name = "copyright", srcs = [ @@ -33,12 +28,13 @@ copyright_checker( visibility = ["//visibility:public"], ) -dash_license_checker( - src = "//examples:cargo_lock", - file_type = "", # let it auto-detect based on project_config - project_config = PROJECT_CONFIG, - visibility = ["//visibility:public"], -) +# TODO: C++ project is unsupported by dash_license_checker for now +# dash_license_checker( +# src = "//examples:cargo_lock", +# file_type = "", # let it auto-detect based on project_config +# project_config = PROJECT_CONFIG, +# visibility = ["//visibility:public"], +# ) # Add target for formatting checks use_format_targets() diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index dcc54e6..6bdc3db 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -23,13 +23,13 @@ In case you want to fix a bug or contribute an improvement, please perform the f 1) Create a PR by using the corresponding template ([Bugfix PR template](.github/PULL_REQUEST_TEMPLATE/bug_fix.md) or [Improvement PR template](.github/PULL_REQUEST_TEMPLATE/improvement.md)). Please mark your PR as draft until it's ready for review by the Committers (see the [Eclipse Foundation Project Handbook](https://www.eclipse.org/projects/handbook/#contributing-committers) for more information on the role definitions). Improvements are requested by the definition or modification of [Stakeholder Requirements](docs/stakeholder_requirements) or [Tool Requirements](docs/tool_requirements) and may be implemented after acceptance/merge of the request by a second Improvement PR. The needed reviews are automatically triggered via the [CODEOWNERS](.github/CODEOWNERS) file in the repository. 2) Initiate content review by opening a corresponding issue for the PR when it is ready for review. Review of the PR and final merge into the project repository is in responsibility of the Committers. Use the [Bugfix Issue template](.github/ISSUE_TEMPLATE/bug_fix.md) or [Improvement Issue template](.github/ISSUE_TEMPLATE/improvement.md) for this. -Please check here for our Git Commit Rules in the [Configuration_Tool_Guidelines](https://eclipse-score.github.io/score/process_description/guidelines/index.html). +Please check here for our Git Commit Rules in the [Git_Guidelines](https://eclipse-score.github.io/score/main/contribute/general/git.html). -Please use the [Stakeholder and Tool Requirements Template](https://eclipse-score.github.io/score/process_description/templates/index.html) when defining these requirements. +Please use the [Stakeholder and Tool Requirements Template](https://eclipse-score.github.io/process_description/main/process_areas/requirements_engineering/guidance/requirements_templates.html) when defining these requirements. ![Contribution guide workflow](./docs/_assets/contribution_guide.svg "Contribution guide workflow") #### Additional Information Please note, that all Git commit messages must adhere the rules described in the [Eclipse Foundation Project Handbook](https://www.eclipse.org/projects/handbook/#resources-commit). -Please find process descriptions here: [process description](https://eclipse-score.github.io/score/process_description/). +Please find process descriptions here: [process description](https://eclipse-score.github.io/process_description/). diff --git a/DEMO.md b/DEMO.md new file mode 100644 index 0000000..641af26 --- /dev/null +++ b/DEMO.md @@ -0,0 +1,87 @@ +# Demo + +Show the PTP-based monotonic clock from application point of view. + +## Overview + +```mermaid +flowchart TB; + demo-app -- IPC --> tsync-daemon + ptpd-slave -- gPTP on eth0 --> ptpd-master + ptpd-slave -- IPC --> tsync-daemon + subgraph ECU/yC + ptpd-master + end +``` + +## Run Demo + +Start the tmux session: +```shell +./demo.sh +``` + +The setup will start 2x PTP instances (one for ECU as master, one for S-Core domain as slave), 1x timesync-daemon (for IPC communication), 1x demo application. All processes will run on the same host, in a normal deployment the time-master would probably be running on a reliable classic ECU. + +The demo application will show `Synchronized` status as long as both PTP instances are running. Temporarily stop one of the instances to simulate a `Timeout` status. + +Modify system clock (e.g. using `sudo examples/shift_time.sh forward`) to show impact on `OS Elapsed` values and that `PTP Elapsed` values will have no impact. + +Navigation: +- Use `Ctrl-b o` to cycle through panes. +- Use `Ctrl-b x` to exit a pane. +- Use `Ctrl-b : kill-session` to exit demo. + +## Manual Setup + +First, build TSYNC-DAEMON as user and start using sudo: +``` +bazel build //src/tsync-daemon:tsync_daemon +export ECUCFG_ENV_VAR_ROOTFOLDER=$(pwd)/bazel-out/k8-fastbuild/bin/src/tsync-daemon/src/score/time/daemon/ +sudo -E bazel-bin/src/tsync-daemon/tsync_daemon +``` + +Second, start the PTP daemon MASTER + +``` +bazel build //src/ptpd +sudo bazel-bin/src/ptpd/ptpd -i eth0 -d 1 --global:foreground=Y -M --ptpengine:transport=ethernet --ptpengine:delay_mechanism=DELAY_DISABLED --ptpengine:disable_bmca=y --score:globaltimepropagationdelay=0.0 -L --ptpengine:dot1as=1 --clock:no_adjust=Y -V +``` + +Start the PTP daemon SLAVE + +``` +sudo bazel-bin/src/ptpd/ptpd -i eth0 -d 1 --global:foreground=Y -s --ptpengine:transport=ethernet --ptpengine:delay_mechanism=DELAY_DISABLED --ptpengine:disable_bmca=y --score:globaltimepropagationdelay=0.0 -L --ptpengine:dot1as=1 --clock:no_adjust=Y -V +``` + +Now start the demo application: + +``` +bazel build //examples:get_current_time +sudo -E bazel-bin/examples/get_current_time +``` + +While the demo application running, modify the system clock to show a drift/jump in the system clock. +Meanwhile, the PTP reported time will be monotonic: + +Expected output +``` +OS=1768400322951429085 PTP=58753506346931 OS Elapsed=86019523725 PTP Elapsed=86019521221 Status=Not synchronized +OS=1768400323951576229 PTP=58754506498519 OS Elapsed=87019670869 PTP Elapsed=87019672809 Status=Not synchronized +OS=1768400324951803426 PTP=58755506721754 OS Elapsed=88019898066 PTP Elapsed=88019896044 Status=Synchronized +OS=1768400325952081179 PTP=58756506998626 OS Elapsed=89020175819 PTP Elapsed=89020172916 Status=Synchronized +OS=1768400326952328115 PTP=58757507248144 OS Elapsed=90020422755 PTP Elapsed=90020422434 Status=Synchronized +<-- OS Clock changed by running shift_time.sh --> +OS=1768400027645599910 PTP=58758507514207 OS Elapsed=-209286305450 PTP Elapsed=91020688497 Status=Synchronized +OS=1768400028645856334 PTP=58759507786837 OS Elapsed=-208286049026 PTP Elapsed=92020961127 Status=Synchronized +OS=1768400029646033445 PTP=58760507948626 OS Elapsed=-207285871915 PTP Elapsed=93021122916 Status=Synchronized +<-- OS Clock changed back --> +OS=1768400330953345151 PTP=58761508256548 OS Elapsed=94021439791 PTP Elapsed=94021430838 Status=Synchronized +OS=1768400331953626191 PTP=58762508539221 OS Elapsed=95021720831 PTP Elapsed=95021713511 Status=Synchronized +OS=1768400332953940464 PTP=58763508852192 OS Elapsed=96022035104 PTP Elapsed=96022026482 Status=Synchronized +OS=1768400333954158292 PTP=58764509072216 OS Elapsed=97022252932 PTP Elapsed=97022246506 Status=Synchronized +OS=1768400334954303570 PTP=58765509214788 OS Elapsed=98022398210 PTP Elapsed=98022389078 Status=Synchronized +OS=1768400335954560065 PTP=58766509471068 OS Elapsed=99022654705 PTP Elapsed=99022645358 Status=Synchronized +``` + +Note that OS Elapsed has jumps whereas PTP Elapsed is continuous. diff --git a/MODULE.bazel b/MODULE.bazel index 1ec606d..1268634 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,15 +10,23 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +# MODULE.bazel +""" +Time Synchronization: Timesync-Daemon, Timesync-Library and PTP-Daemon +""" + module( - name = "cpp_rust_template_repository", - version = "1.0", + name = "score-timesync", + version = "0.0.1", + compatibility_level = 1, ) -bazel_dep(name = "rules_python", version = "1.4.1") +# For ecucfgdsl_processor.sh +bazel_dep(name = "rules_sh", version = "0.5.0") +# For docs +bazel_dep(name = "rules_python", version = "1.4.1") PYTHON_VERSION = "3.12" - python = use_extension("@rules_python//python/extensions:python.bzl", "python") python.toolchain( is_default = True, @@ -27,31 +35,20 @@ python.toolchain( use_repo(python) # Add GoogleTest dependency -bazel_dep(name = "googletest", version = "1.17.0") +bazel_dep(name = "googletest", version = "1.17.0", dev_dependency = True) -# Rust rules for Bazel -bazel_dep(name = "rules_rust", version = "0.63.0") - -# C/C++ rules for Bazel -bazel_dep(name = "rules_cc", version = "0.2.1") - -# LLVM Toolchains Rules - host configuration -bazel_dep(name = "toolchains_llvm", version = "1.4.0") - -llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") -llvm.toolchain( - cxx_standard = {"": "c++17"}, - llvm_version = "19.1.0", -) -use_repo(llvm, "llvm_toolchain") -use_repo(llvm, "llvm_toolchain_llvm") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "flatbuffers", version = "25.2.10") -register_toolchains("@llvm_toolchain//:all") +# Eclipse S-CORE +bazel_dep(name = "score_baselibs", version = "0.2.2") # tooling -bazel_dep(name = "score_tooling", version = "1.0.1") +bazel_dep(name = "score_tooling", version = "1.0.4") bazel_dep(name = "aspect_rules_lint", version = "1.5.3") bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") #docs-as-code -bazel_dep(name = "score_docs_as_code", version = "1.1.0") +bazel_dep(name = "score_docs_as_code", version = "2.2.0") +# To reference process requirements +bazel_dep(name = "score_process", version = "1.4.0") diff --git a/README.md b/README.md index 1e8af75..56b2a46 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,110 @@ - -# C++ & Rust Bazel Template Repository - -This repository serves as a **template** for setting up **C++ and Rust projects** using **Bazel**. -It provides a **standardized project structure**, ensuring best practices for: - -- **Build configuration** with Bazel. -- **Testing** (unit and integration tests). -- **Documentation** setup. -- **CI/CD workflows**. -- **Development environment** configuration. - ---- - -## 📂 Project Structure - -| File/Folder | Description | -| ----------------------------------- | ------------------------------------------------- | -| `README.md` | Short description & build instructions | -| `src/` | Source files for the module | -| `tests/` | Unit tests (UT) and integration tests (IT) | -| `examples/` | Example files used for guidance | -| `docs/` | Documentation (Doxygen for C++ / mdBook for Rust) | -| `.github/workflows/` | CI/CD pipelines | -| `.vscode/` | Recommended VS Code settings | -| `.bazelrc`, `MODULE.bazel`, `BUILD` | Bazel configuration & settings | -| `project_config.bzl` | Project-specific metadata for Bazel macros | -| `LICENSE.md` | Licensing information | -| `CONTRIBUTION.md` | Contribution guidelines | - ---- +# Time Synchronization + +Time Synchronization between different applications and/or ECUs is of paramount importance when correlation of different +events across a distributed system is needed, either to track such events in time or to trigger them at an accurate point +in time. + +The Time-Synchronization (TSync) module is responsible for providing users with the functionality to retrieve time +information synchronized with other entities / ECUs. + +## Project Structure + +This C++ code implements the *Time Synchronization* module for S-CORE. +The folder structure is: + +```plain +score-time + ├── examples tsync-lib usage examples + ├── src + | ├── common Common functionality used by "all" packages + │ ├── flatbuffer Flatbuffers mod for coverage(?) + │   ├── flatcfg Flatcfg implementation (to be moved to a common repo) + │ ├── ptpd Modified variant of ptpd + │   ├── tsync-daemon Daemon for managing tsync shared mem + │   ├── tsync-lib Library to be used by time clients + │   ├── tsync-ptp-lib Library used by ptpd + │   └── tsync-utility-lib Internal utility library for shared mem access + ├── tests + └── Binary/integration tests +``` +Common sub-structure of each component (if applicable): +* Folder `include` contains the API definition of the component (i.e. its public headers). +* Folder `src` contains source files of the implementation and private headers. +* Folder `test` contains unit tests for the implemented classes. ## 🚀 Getting Started ### 1️⃣ Clone the Repository ```sh -git clone https://github.com/eclipse-score/YOUR_PROJECT.git -cd YOUR_PROJECT +git clone https://github.com/eclipse-score/inc_time.git +cd inc_time ``` -### 2️⃣ Build the Examples of module +## 2️⃣ Build -> DISCLAIMER: Depending what module implements, it's possible that different -> configuration flags needs to be set on command line. +In order to build and run `ptpd`, install `libpcap`: -To build all targets of the module the following command can be used: - -```sh -bazel build //src/... +```shell +sudo apt-get install libpcap-dev ``` -This command will instruct Bazel to build all targets that are under Bazel -package `src/`. The ideal solution is to provide single target that builds -artifacts, for example: +To build TSYNC, simply run Bazel: -```sh -bazel build //src/:release_artifacts +```shell +bazel build //... ``` - -where `:release_artifacts` is filegroup target that collects all release -artifacts of the module. - -> NOTE: This is just proposal, the final decision is on module maintainer how -> the module code needs to be built. - ### 3️⃣ Run Tests ```sh -bazel test //tests/... +bazel test //src/... ``` ---- - -## 🛠 Tools & Linters - -The template integrates **tools and linters** from **centralized repositories** to ensure consistency across projects. - -- **C++:** `clang-tidy`, `cppcheck`, `Google Test` -- **Rust:** `clippy`, `rustfmt`, `Rust Unit Tests` -- **CI/CD:** GitHub Actions for automated builds and tests - ---- +## 🛠 Usage -## 📖 Documentation +Run the timesync daemon: -- A **centralized docs structure** is planned. +```shell +export ECUCFG_ENV_VAR_ROOTFOLDER=$(pwd)/bazel-out/k8-fastbuild/bin/src/tsync-daemon/src/ +bazel run //src/tsync-daemon:tsync_daemon +``` ---- +Run the ptp daemon: -## ⚙️ `project_config.bzl` +```shell +sudo bazelisk run //src/ptpd -- -i eth0 -d 1 --global:foreground=Y -S --ptpengine:transport=ethernet --ptpengine:delay_mechanism=DELAY_DISABLED --ptpengine:disable_bmca=y --score:globaltimepropagationdelay=0.0 -L --ptpengine:dot1as=1 --clock:no_adjust=Y +``` -This file defines project-specific metadata used by Bazel macros, such as `dash_license_checker`. +## High Level Design -### 📌 Purpose +The basic architecture/high level design of the S-CORE time related components is given in the following figure: -It provides structured configuration that helps determine behavior such as: +
+ +
+Deployment: This figure shows an exemplary deployment of the time sync related components. +
+
-- Source language type (used to determine license check file format) -- Safety level or other compliance info (e.g. ASIL level) +* S-CORE time can handle multiple different time bases/time domains. +* Syncing of the "clocks" of those time bases between different devices/exeution environments is done via PTP over Ethernet +* A modified ptpd2 implementation is used to achive that on the S-CORE domain: + * It syncs a time base with its belonging master clock + * It stores the determined time offset between the time base and the local clock in a share memory area related to the time base + * It does *not* sync the local clock time! + * The modifications are done for supporting the AUTOSAR Time Synchronization Protocol +* Applications use the tsync-lib to get the current time of the desired time base. The tsync-lib + * gets the time base's offset to the local clock from the respective shared memory ressource and + * adds it to the current local clock value. +* The tsync-daemon is responsible to + * manage the shared memory ressources of the different configured time bases + * provide the time base configuration via shared memory to the applications and the ptpd2 process -### 📄 Example Content -```python -PROJECT_CONFIG = { - "asil_level": "QM", # or "ASIL-A", "ASIL-B", etc. - "source_code": ["cpp", "rust"] # Languages used in the module -} -``` +## 📖 Documentation -### 🔧 Use Case +- Feature: +- Module Documentation: +- Requirements: -When used with macros like `dash_license_checker`, it allows dynamic selection of file types - (e.g., `cargo`, `requirements`) based on the languages declared in `source_code`. +--- diff --git a/demo.sh b/demo.sh new file mode 100755 index 0000000..bf3535e --- /dev/null +++ b/demo.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# (c) 2025 ETAS GmbH. All rights reserved. + +SESSION_NAME="score_time_demo" +APP_DIR="$(pwd)" + +# --- Step 1: Build all applications with Bazel --- +echo "Building all applications with Bazel..." +bazel build ///... + +if [ $? -ne 0 ]; then + echo "Bazel build failed. Exiting." + exit 1 +fi + +echo "Bazel build successful!" + +echo "Cleaning up in case there are leftovers..." +sudo killall ptpd +sudo killall tsync_daemon +sudo rm -f /dev/shm/sem.time_domain_1 +sudo rm -f /dev/shm/sysclock +sudo rm -f /dev/shm/tsync_id_mappings + +echo "Starting tmux session..." +sleep 2 + +# --- Step 2: Create a new tmux session and window --- +tmux new-session -d -s $SESSION_NAME -n "Eclipse S-Core: TIME demo" + +tmux set -g pane-border-status top + +# --- Step 3: Split the window into 4 panes --- +# Layout: +# -------------------- +# | P1 | +# | | +# -------------------- +# | P2 | P3 | P4 | +# -------------------- + +# Split top/bottom (splits into two horizontal panes) +# Pane 0 (top), Pane 1 (bottom) +tmux split-window -v -t $SESSION_NAME:0.0 + +# Select the bottom pane (Pane 1) and split it vertically into three +# This will result in: +# Pane 0 (top) +# Pane 1 (bottom left) +# Pane 2 (bottom middle) +tmux split-window -h -t $SESSION_NAME:0.1 +tmux split-window -h -t $SESSION_NAME:0.2 + +# Now we have 4 panes: +# Pane 0: Top (for get_current_time) +# Pane 1: Bottom-Left (for tsync-daemon) +# Pane 2: Bottom-Middle (for ptpd_master) +# Pane 3: Bottom-Right (for ptpd_slave) + +# --- Step 4: Run each application in its respective pane --- + +# Pane 0: get_current_time +tmux select-pane -t $SESSION_NAME:0.0 -T "get_current_time.cpp" +tmux send-keys -t $SESSION_NAME:0.0 "echo '--- get_current_time (Application) ---'" C-m +tmux send-keys -t $SESSION_NAME:0.0 "cd $APP_DIR && sleep 2" C-m +tmux send-keys -t $SESSION_NAME:0.0 "sudo -E bazel-bin/examples/get_current_time" C-m + +# Pane 1: tsync-daemon +tmux select-pane -t $SESSION_NAME:0.1 -T "TSYNC-DAEMON" +tmux send-keys -t $SESSION_NAME:0.1 "echo '--- tsync-daemon ---'" C-m +tmux send-keys -t $SESSION_NAME:0.1 "cd $APP_DIR" C-m +tmux send-keys -t $SESSION_NAME:0.1 "export ECUCFG_ENV_VAR_ROOTFOLDER=$APP_DIR/bazel-out/k8-fastbuild/bin/src/tsync-daemon/src/score/time/daemon/" C-m +tmux send-keys -t $SESSION_NAME:0.1 "sudo -E bazel-bin/src/tsync-daemon/tsync_daemon" C-m + +# Pane 2: ptpd_master +tmux select-pane -t $SESSION_NAME:0.2 -T "PTP MASTER (e.g. on µC Classic domain)" +tmux send-keys -t $SESSION_NAME:0.2 "echo '--- ptpd master ---'" C-m +tmux send-keys -t $SESSION_NAME:0.2 "cd $APP_DIR && sleep 1" C-m +tmux send-keys -t $SESSION_NAME:0.2 "sudo bazel-bin/src/ptpd/ptpd -i eth0 -d 1 --global:foreground=Y -M --ptpengine:transport=ethernet --ptpengine:delay_mechanism=DELAY_DISABLED --ptpengine:disable_bmca=y --score:globaltimepropagationdelay=0.0 -L --ptpengine:dot1as=1 --clock:no_adjust=Y -V" C-m + +# Pane 3: ptpd_slave +tmux select-pane -t $SESSION_NAME:0.3 -T "PTP SLAVE (e.g. on S-Core domain)" +tmux send-keys -t $SESSION_NAME:0.3 "echo '--- ptpd slave ---'" C-m +tmux send-keys -t $SESSION_NAME:0.3 "cd $APP_DIR && sleep 1" C-m +tmux send-keys -t $SESSION_NAME:0.3 "sudo bazel-bin/src/ptpd/ptpd -i eth0 -d 1 --global:foreground=Y -s --ptpengine:transport=ethernet --ptpengine:delay_mechanism=DELAY_DISABLED --ptpengine:disable_bmca=y --score:globaltimepropagationdelay=0.0 -L --ptpengine:dot1as=1 --clock:no_adjust=Y -V" C-m + +# --- Attach to the tmux session --- +echo "Attaching to tmux session '$SESSION_NAME'. Press Ctrl+B D to detach." +tmux attach-session -t $SESSION_NAME diff --git a/docs/conf.py b/docs/conf.py index cf13475..652c839 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) 2025 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -20,9 +20,9 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "Module Template Project" -project_url = "https://eclipse-score.github.io/module_template/" -project_prefix = "MODULE_TEMPLATE_" +project = "S-Core Time Synchronization" +project_url = "https://eclipse-score.github.io/inc_time/" +project_prefix = "TIME_" author = "S-CORE" version = "0.1" diff --git a/docs/deployment.drawio.svg b/docs/deployment.drawio.svg new file mode 100644 index 0000000..172a25c --- /dev/null +++ b/docs/deployment.drawio.svg @@ -0,0 +1,4 @@ + + + +
«execution environment»
S-CORE-based
«process»
Application1
«process»
Application2

Shared Memory          
«process»
tsync-daemon
«process»
ptpd2
time base
configuration
time base A
sync data
time base B
sync data
...
...
«execution environment»
Some ECU, typically µC based
Time Master
PTP (over Ethernet)
tsync-lib
tsync-lib
tsync-ptp-lib
\ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index f8e53da..af2b478 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ .. # ******************************************************************************* - # Copyright (c) 2024 Contributors to the Eclipse Foundation + # Copyright (c) 2025 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -12,14 +12,16 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -Module Template Documentation -============================= +Time Synchronization +==================== -This documentation describes the structure, usage and configuration of the Bazel-based C++/Rust module template. +This documentation describes the structure, usage and configuration of the Time Synchronization module. -.. contents:: Table of Contents - :depth: 2 - :local: +.. toctree:: + :maxdepth: 5 + + stakeholder_requirements/index + tool_requirements/index Overview -------- @@ -27,24 +29,12 @@ Overview This repository provides a standardized setup for projects using **C++** or **Rust** and **Bazel** as a build system. It integrates best practices for build, test, CI/CD and documentation. -Requirements ------------- - -.. stkh_req:: Example Functional Requirement - :id: stkh_req__docgen_enabled__example - :status: valid - :safety: QM - :security: YES - :reqtype: Functional - :rationale: Ensure documentation builds are possible for all modules - - Project Layout -------------- The module template includes the following top-level structure: -- `src/`: Main C++/Rust sources +- `src/`: Main C++/C sources - `tests/`: Unit and integration tests - `examples/`: Usage examples - `docs/`: Documentation using `docs-as-code` @@ -76,7 +66,7 @@ Example: PROJECT_CONFIG = { "asil_level": "QM", - "source_code": ["cpp", "rust"] + "source_code": ["cpp"] } This enables conditional behavior (e.g., choosing `clang-tidy` for C++ or `clippy` for Rust). diff --git a/docs/stakeholder_requirements/index.rst b/docs/stakeholder_requirements/index.rst new file mode 100644 index 0000000..f31488e --- /dev/null +++ b/docs/stakeholder_requirements/index.rst @@ -0,0 +1,25 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Stakeholder Requirements +------------------------ + +.. stkh_req:: Vehicle Time synchronization + :id: stkh_req__time__vehicle_time_sync + :reqtype: Functional + :security: NO + :safety: QM + :rationale: Synchronize local clock with an external time master using gPTP protocol (IEEE 802.1AS) + :valid_from: v1.0.0 + :status: valid diff --git a/docs/todo.txt b/docs/todo.txt new file mode 100644 index 0000000..7e6afcf --- /dev/null +++ b/docs/todo.txt @@ -0,0 +1,17 @@ + +Unit tests: +* Enable tests of tsync-ptp-lib +* Enable tests of ptpd + +Config: +* Move config in tsync-daemon to a separate place, "at least" `src/tsync-daemon/cfg/` + +Flatbuffers: +* Replace by S-CORE solution and remove folders `flatbuffer`+ `flatcfg` +* Move stuff in `common` to tsync-utility-lib + +Coverage: +* Enable coverage + +Renamings: +* Rename tsync-* to time-* ?? diff --git a/docs/tool_requirements/index.rst b/docs/tool_requirements/index.rst new file mode 100644 index 0000000..50d451a --- /dev/null +++ b/docs/tool_requirements/index.rst @@ -0,0 +1,25 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Tool Requirements +----------------- + +.. tool_req:: Build Tool + :id: tool_req__build + :security: NO + :safety: QM + :status: valid + :implemented: YES + + Build tool for building the module. diff --git a/examples/BUILD b/examples/BUILD index 012dd54..b0be179 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -1,8 +1,14 @@ -# Needed for Dash tool to check python dependency licenses. -filegroup( - name = "cargo_lock", - srcs = [ - "Cargo.lock", - ], - visibility = ["//visibility:public"], +# examples/BUILD + +# Load necessary Bazel rules +load("@rules_cc//cc:defs.bzl", "cc_binary") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "get_current_time", + srcs = ["get_current_time.cpp"], + deps = [ + "//src/tsync-lib:tsync", + ] ) diff --git a/examples/get_current_time.cpp b/examples/get_current_time.cpp new file mode 100644 index 0000000..1ecfc5a --- /dev/null +++ b/examples/get_current_time.cpp @@ -0,0 +1,84 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +// For S-Core Time +#include "score/time/synchronized_time_base_consumer.h" +#include "score/time/synchronized_time_base_status.h" + +// For OS Time and main loop +#include // For std::chrono +#include // For std::this_thread::sleep_for + +// Handle Ctrl-C to abort the loop +#include +static volatile std::sig_atomic_t keep_running = true; +void signal_handler(int signum) { + keep_running = false; +} + +using nanoseconds = std::chrono::nanoseconds; + +int main() { + std::signal(SIGINT, signal_handler); + std::string_view instance_specifier = "consumer"; + + std::cout << "Instantiate a SynchronizedTimeBaseConsumer for instance '" + << instance_specifier << "'" << std::endl; + score::time::SynchronizedTimeBaseConsumer stbc{instance_specifier}; + + auto os_first = std::chrono::high_resolution_clock::now(); + auto os_first_ns = std::chrono::duration_cast(os_first.time_since_epoch()).count(); + auto stbc_first = stbc.GetCurrentTime(); + auto stbc_first_ns = stbc_first.time_since_epoch().count(); + + while (keep_running) { + // Get current timestamp from the operating system + auto os_timestamp = std::chrono::high_resolution_clock::now(); + auto stbc_timestamp = stbc.GetCurrentTime(); + + auto stbc_timestamp_with_status = stbc.GetTimeWithStatus(); + auto stbc_sync_status = stbc_timestamp_with_status.GetSynchronizationStatus(); + + // auto stbc_timestamp_duration = stbc.GetCurrentTime().time_since_epoch().count(); + auto os_ns = std::chrono::duration_cast(os_timestamp.time_since_epoch()).count(); + auto stbc_ns = stbc_timestamp.time_since_epoch().count(); + + // Calculate the delta + auto elapsed_ns = os_ns - os_first_ns; + auto stbc_elapsed_ns = stbc_ns - stbc_first_ns; + + auto status = "Unavailable"; + if (stbc_sync_status == score::time::SynchronizationStatus::kNotSynchronizedUntilStartup) { + status = "Not synchronized until startup"; + } else if (stbc_sync_status == score::time::SynchronizationStatus::kTimeOut) { + status = "Timeout"; + } else if (stbc_sync_status == score::time::SynchronizationStatus::kSynchronized) { + status = "Synchronized"; + } else if (stbc_sync_status == score::time::SynchronizationStatus::kSynchToGateway) { + status = "Synchronized to Gateway"; + } else { + status = "Unknown"; + } + + std::cout + << "OS=" + << os_ns + << " PTP=" + << stbc_ns + << " OS Elapsed=" + << elapsed_ns + << " PTP Elapsed=" + << stbc_elapsed_ns + << " Status=" + << status + << std::endl; + + // Wait for 1 second + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + return 0; +} diff --git a/examples/shift_time.sh b/examples/shift_time.sh new file mode 100755 index 0000000..1c51236 --- /dev/null +++ b/examples/shift_time.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Configuration +TIME_SHIFT_MINUTES=5 +NTP_SERVICE="systemd-timesyncd" # Change this to your NTP service: chronyd, ntp, systemd-timesyncd + +# --- Function to display usage --- +usage() { + echo "Usage: $0 [forward|backward]" + echo " 'forward' : Moves the system clock forward by ${TIME_SHIFT_MINUTES} minutes." + echo " 'backward' : Moves the system clock backward by ${TIME_SHIFT_MINUTES} minutes." + exit 1 +} + +# --- Ensure script is run as root --- +if [ "$EUID" -ne 0 ]; then + echo "Error: Please run as root (sudo)." + exit 1 +fi + +# --- Process command-line arguments --- +if [ -z "$1" ]; then + usage +fi + +DIRECTION="$1" +if [ "$DIRECTION" != "forward" ] && [ "$DIRECTION" != "backward" ]; then + usage +fi + +# --- Stop NTP/Time Sync Service --- +echo "Attempting to stop time synchronization service: ${NTP_SERVICE}..." +systemctl stop "${NTP_SERVICE}" &>/dev/null || { + echo "Warning: Could not stop ${NTP_SERVICE}. It might not be installed, running, or you need to adjust the NTP_SERVICE variable." + echo "Continuing without stopping NTP, but time changes might be reverted quickly." +} +sleep 1 # Give it a moment to stop + +# --- Get Current Time in seconds since epoch --- +CURRENT_EPOCH_SECONDS=$(date +%s) +echo "Current system time: $(date -d "@${CURRENT_EPOCH_SECONDS}")" + +# --- Calculate New Time --- +SHIFT_SECONDS=$((TIME_SHIFT_MINUTES * 60)) +NEW_EPOCH_SECONDS=0 + +if [ "$DIRECTION" == "forward" ]; then + NEW_EPOCH_SECONDS=$((CURRENT_EPOCH_SECONDS + SHIFT_SECONDS)) + echo "Calculating new time: ${TIME_SHIFT_MINUTES} minutes forward..." +elif [ "$DIRECTION" == "backward" ]; then + NEW_EPOCH_SECONDS=$((CURRENT_EPOCH_SECONDS - SHIFT_SECONDS)) + echo "Calculating new time: ${TIME_SHIFT_MINUTES} minutes backward..." +fi + +NEW_TIME_STRING=$(date -d "@${NEW_EPOCH_SECONDS}" +"%Y-%m-%d %H:%M:%S") + +# --- Set New System Time --- +echo "Changing system time to: ${NEW_TIME_STRING}" +date -s "$NEW_TIME_STRING" +echo "Verification: $(date)" + +# --- Prompt for user action or a short wait --- +echo "" +echo "The system time has been shifted. Perform your tests now." +echo "Press Enter to revert the time, or wait 10 seconds for automatic revert..." +read -t 10 -p "" && REVERT_NOW=true + +if [ -z "$REVERT_NOW" ]; then + echo "Automatic revert initiated after 10 seconds." +fi + +# --- Restart NTP/Time Sync Service --- +echo "" +echo "Attempting to restart time synchronization service: ${NTP_SERVICE}..." +systemctl start "${NTP_SERVICE}" &>/dev/null || { + echo "Error: Could not restart ${NTP_SERVICE}. You may need to manually restart it or set time with 'sudo date -s \"$(date)\"' to current." +} + +echo "Time synchronization service restarted. System time should now be accurate." +echo "Final verification: $(date)" +echo "Done." diff --git a/project_config.bzl b/project_config.bzl index f764a1d..8f203ed 100644 --- a/project_config.bzl +++ b/project_config.bzl @@ -1,5 +1,5 @@ # project_config.bzl PROJECT_CONFIG = { "asil_level": "QM", - "source_code": ["rust"], + "source_code": ["cpp"], } diff --git a/run_coverage_local.sh b/run_coverage_local.sh new file mode 100755 index 0000000..837ffb3 --- /dev/null +++ b/run_coverage_local.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Script to run code coverage locally + +set -e # Exit on error + +echo "Running code coverage analysis..." +bazel coverage //src/... + +echo "" +echo "Generating HTML coverage report..." +OUTPUT_PATH=$(bazel info output_path) + +if [ -f "${OUTPUT_PATH}/_coverage/_coverage_report.dat" ]; then + + genhtml "${OUTPUT_PATH}/_coverage/_coverage_report.dat" \ + -o=cpp_coverage \ + --show-details \ + --legend \ + --function-coverage \ + --branch-coverage + + echo "" + echo " Coverage report generated successfully!" + echo " Location: cpp_coverage/" + + if command -v xdg-open &> /dev/null; then + xdg-open cpp_coverage/index.html + fi +else + echo "Error: Coverage data file not found at ${OUTPUT_PATH}/_coverage/_coverage_report.dat" + exit 1 +fi diff --git a/src/BUILD b/src/BUILD deleted file mode 100644 index e69de29..0000000 diff --git a/src/common/BUILD b/src/common/BUILD new file mode 100644 index 0000000..44eb96c --- /dev/null +++ b/src/common/BUILD @@ -0,0 +1,29 @@ +# src/score/time/lib/BUILD +# include/BUILD + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "common_if", + hdrs = glob([ + "include/**/*.h", + ]), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) + +cc_library( + name = "common", + linkstatic = True, + hdrs = [ + ":common_if", + ], + srcs = glob([ + "src/**/*.cpp", + ]), + copts = ["-iquote src/common/src"], + deps = [ + ":common_if", + ], + visibility = ["//visibility:public"], +) diff --git a/src/common/include/score/time/common/Abort.h b/src/common/include/score/time/common/Abort.h new file mode 100644 index 0000000..83c6871 --- /dev/null +++ b/src/common/include/score/time/common/Abort.h @@ -0,0 +1,18 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_COMMON_ABORT_H_ +#define SCORE_TIME_COMMON_ABORT_H_ + +namespace score { +namespace time { +namespace common { + +[[noreturn]] void logFatalAndAbort(const char* msg) noexcept; + +} // namespace commmon +} // namespace time +} // namespace score + +#endif // SCORE_TIME_COMMON_ABORT_H_ diff --git a/src/common/include/score/time/common/ExcludeCoverageAdapter.h b/src/common/include/score/time/common/ExcludeCoverageAdapter.h new file mode 100644 index 0000000..bfe5b55 --- /dev/null +++ b/src/common/include/score/time/common/ExcludeCoverageAdapter.h @@ -0,0 +1,20 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_COMMON_EXCLUDECOVERAGEADAPTER_H_ +#define SCORE_TIME_COMMON_EXCLUDECOVERAGEADAPTER_H_ + +#ifdef __CTC__ +#define EXCLUDE_COVERAGE_START(justification) _Pragma("CTC ANNOTATION justification") _Pragma("CTC SKIP") +#else +#define EXCLUDE_COVERAGE_START(justification) +#endif + +#ifdef __CTC__ +#define EXCLUDE_COVERAGE_END _Pragma("CTC ENDSKIP") +#else +#define EXCLUDE_COVERAGE_END +#endif + +#endif // SCORE_TIME_COMMON_EXCLUDECOVERAGEADAPTER_H_ diff --git a/src/common/src/score/time/common/Abort.cpp b/src/common/src/score/time/common/Abort.cpp new file mode 100644 index 0000000..0196fb9 --- /dev/null +++ b/src/common/src/score/time/common/Abort.cpp @@ -0,0 +1,21 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include +//!!#include "score/mw/log/logging.h" + +namespace score { +namespace time { +namespace common { + +[[noreturn]] void logFatalAndAbort(const char* msg) noexcept { + //!!score::mw::LogFatal() << msg << std::endl; + std::cerr << "FATAL: " << msg << std::endl; + std::abort(); +} + +} // namespace commmon +} // namespace time +} // namespace score diff --git a/src/flatbuffer/BUILD b/src/flatbuffer/BUILD new file mode 100644 index 0000000..80ab39d --- /dev/null +++ b/src/flatbuffer/BUILD @@ -0,0 +1,12 @@ +# include/BUILD + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "flatbuffer", + hdrs = glob([ + "include/**/*.h", + ]), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) diff --git a/src/flatbuffer/include/flatbuffers/base.h b/src/flatbuffer/include/flatbuffers/base.h new file mode 100644 index 0000000..a1be0e8 --- /dev/null +++ b/src/flatbuffer/include/flatbuffers/base.h @@ -0,0 +1,361 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATBUFFERS_BASE_H_ +#define FLATBUFFERS_BASE_H_ + +// clang-format off + +// Add to the end of a macro definition to force using a macro as a statement +#define FLATBUFFERS_REQUIRE_SEMICOLON static_assert(true, "") + +// Necessary because _Pragma("x" "y") won't work directly +#define FLATBUFFERS_DO_PRAGMA_(x) _Pragma(#x) +#define FLATBUFFERS_DO_PRAGMA(x) FLATBUFFERS_DO_PRAGMA_(x) + +// #ifdef __CTC__ +// #define FLATBUFFERS_CTC_ANNOTATION(justification_literal) FLATBUFFERS_DO_PRAGMA(CTC ANNOTATION justification_literal) FLATBUFFERS_REQUIRE_SEMICOLON +// #else + #define FLATBUFFERS_CTC_ANNOTATION(justification_literal) FLATBUFFERS_REQUIRE_SEMICOLON +// #endif + +#ifdef __CTC__ + #define FLATBUFFERS_EXCLUDE_COVERAGE_START(justification_literal) \ + FLATBUFFERS_CTC_ANNOTATION(justification_literal); _Pragma("CTC SKIP") FLATBUFFERS_REQUIRE_SEMICOLON +#else + #define FLATBUFFERS_EXCLUDE_COVERAGE_START(justification_literal) FLATBUFFERS_REQUIRE_SEMICOLON +#endif + +#ifdef __CTC__ + #define FLATBUFFERS_EXCLUDE_COVERAGE_END() _Pragma("CTC ENDSKIP") FLATBUFFERS_REQUIRE_SEMICOLON +#else + #define FLATBUFFERS_EXCLUDE_COVERAGE_END() FLATBUFFERS_REQUIRE_SEMICOLON +#endif + +// If activate should be declared and included first. +#if defined(FLATBUFFERS_MEMORY_LEAK_TRACKING) && \ + defined(_MSC_VER) && defined(_DEBUG) + // The _CRTDBG_MAP_ALLOC inside will replace + // calloc/free (etc) to its debug version using #define directives. + #define _CRTDBG_MAP_ALLOC + #include + #include + // Replace operator new by trace-enabled version. + #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) + #define new DEBUG_NEW +#endif + +#if !defined(FLATBUFFERS_ASSERT) +#define FLATBUFFERS_ASSERT static_cast +#elif defined(FLATBUFFERS_ASSERT_INCLUDE) +// Include file with forward declaration +#include FLATBUFFERS_ASSERT_INCLUDE +#endif + +#ifndef ARDUINO +#include +#endif + +#include +#include +#include + +#if defined(ARDUINO) && !defined(ARDUINOSTL_M_H) + #include +#else + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(__ICCARM__) +#include +#endif + +// Note the __clang__ check is needed, because clang presents itself +// as an older GNUC compiler (4.2). +// Clang 3.3 and later implement all of the ISO C++ 2011 standard. +// Clang 3.4 and later implement all of the ISO C++ 2014 standard. +// http://clang.llvm.org/cxx_status.html + +// Note the MSVC value '__cplusplus' may be incorrect: +// The '__cplusplus' predefined macro in the MSVC stuck at the value 199711L, +// indicating (erroneously!) that the compiler conformed to the C++98 Standard. +// This value should be correct starting from MSVC2017-15.7-Preview-3. +// The '__cplusplus' will be valid only if MSVC2017-15.7-P3 and the `/Zc:__cplusplus` switch is set. +// Workaround (for details see MSDN): +// Use the _MSC_VER and _MSVC_LANG definition instead of the __cplusplus for compatibility. +// The _MSVC_LANG macro reports the Standard version regardless of the '/Zc:__cplusplus' switch. + +#if defined(__GNUC__) && !defined(__clang__) + #define FLATBUFFERS_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#else + #define FLATBUFFERS_GCC 0 +#endif + +#if defined(__clang__) + #define FLATBUFFERS_CLANG (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) +#else + #define FLATBUFFERS_CLANG 0 +#endif + +// The wire format uses a little endian encoding (since that's efficient for +// the common platforms). +#if defined(__s390x__) + #define FLATBUFFERS_LITTLEENDIAN 0 +#endif // __s390x__ +#if !defined(FLATBUFFERS_LITTLEENDIAN) + #if defined(__GNUC__) || defined(__clang__) || defined(__ICCARM__) + #if (defined(__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + #define FLATBUFFERS_LITTLEENDIAN 0 + #else + #define FLATBUFFERS_LITTLEENDIAN 1 + #endif // __BIG_ENDIAN__ + #elif defined(_MSC_VER) + #if defined(_M_PPC) + #define FLATBUFFERS_LITTLEENDIAN 0 + #else + #define FLATBUFFERS_LITTLEENDIAN 1 + #endif + #else + #error Unable to determine endianness, define FLATBUFFERS_LITTLEENDIAN. + #endif +#endif // !defined(FLATBUFFERS_LITTLEENDIAN) + +#define FLATBUFFERS_VERSION_MAJOR 25 +#define FLATBUFFERS_VERSION_MINOR 2 +#define FLATBUFFERS_VERSION_REVISION 10 +//SCORE Extension start +#define FLATBUFFERS_VERSION_SCORE_MAJOR 0 +#define FLATBUFFERS_VERSION_SCORE_MINOR 1 +//SCORE Extension end +#define FLATBUFFERS_STRING_EXPAND(X) #X +#define FLATBUFFERS_STRING(X) FLATBUFFERS_STRING_EXPAND(X) +namespace flatbuffers { + // Returns version as string "MAJOR.MINOR.REVISION". + const char* FLATBUFFERS_VERSION(); +} + +// Available since C++14 at the latest. +#define FLATBUFFERS_FINAL_CLASS final +#define FLATBUFFERS_OVERRIDE override +#define FLATBUFFERS_VTABLE_UNDERLYING_TYPE : flatbuffers::voffset_t +#define FLATBUFFERS_CONSTEXPR constexpr +#define FLATBUFFERS_CONSTEXPR_CPP14 FLATBUFFERS_CONSTEXPR +#define FLATBUFFERS_NOEXCEPT noexcept +#define FLATBUFFERS_DELETE_FUNC(func) func = delete; + +#ifndef FLATBUFFERS_HAS_STRING_VIEW + // Only provide flatbuffers::string_view if __has_include can be used + // to detect a header that provides an implementation + #if defined(__has_include) + // Check for std::string_view (in c++17) + #if __has_include() && (__cplusplus >= 201606 || (defined(_HAS_CXX17) && _HAS_CXX17)) + #include + namespace flatbuffers { + using string_view = std::string_view; + } + #define FLATBUFFERS_HAS_STRING_VIEW 1 + // Check for std::experimental::string_view (in c++14, compiler-dependent) + #elif __has_include() && (__cplusplus >= 201411) + #include + namespace flatbuffers { + using string_view = std::experimental::string_view; + } + #define FLATBUFFERS_HAS_STRING_VIEW 1 + // Check for absl::string_view + #elif __has_include("absl/strings/string_view.h") + #include "absl/strings/string_view.h" + namespace flatbuffers { + using string_view = absl::string_view; + } + #define FLATBUFFERS_HAS_STRING_VIEW 1 + #endif + #endif // __has_include +#endif // !FLATBUFFERS_HAS_STRING_VIEW + +// Available since C++11. +#define FLATBUFFERS_HAS_NEW_STRTOD 1 + +#ifndef FLATBUFFERS_LOCALE_INDEPENDENT + // Enable locale independent functions {strtof_l, strtod_l,strtoll_l, strtoull_l}. + // They are part of the POSIX-2008 but not part of the C/C++ standard. + // GCC/Clang have definition (_XOPEN_SOURCE>=700) if POSIX-2008. + #if ((defined(_MSC_VER) && _MSC_VER >= 1800) || \ + (defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE>=700))) + #define FLATBUFFERS_LOCALE_INDEPENDENT 1 + #else + #define FLATBUFFERS_LOCALE_INDEPENDENT 0 + #endif +#endif // !FLATBUFFERS_LOCALE_INDEPENDENT + +// Suppress Undefined Behavior Sanitizer (recoverable only). Usage: +// - __supress_ubsan__("undefined") +// - __supress_ubsan__("signed-integer-overflow") +#if defined(__clang__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >=7)) + #define __supress_ubsan__(type) __attribute__((no_sanitize(type))) +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409) + #define __supress_ubsan__(type) __attribute__((no_sanitize_undefined)) +#else + #define __supress_ubsan__(type) +#endif + +// This is constexpr function used for checking compile-time constants. +// Avoid `#pragma warning(disable: 4127) // C4127: expression is constant`. +template FLATBUFFERS_CONSTEXPR inline bool IsConstTrue(T t) { + return !!t; +} + +// Enable C++ attribute [[]] if std:c++17 or higher. +#if ((__cplusplus >= 201703L) \ + || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + // All attributes unknown to an implementation are ignored without causing an error. + #define FLATBUFFERS_ATTRIBUTE(attr) [[attr]] + + #define FLATBUFFERS_FALLTHROUGH() [[fallthrough]] +#else + #define FLATBUFFERS_ATTRIBUTE(attr) + + #if FLATBUFFERS_CLANG >= 30800 + #define FLATBUFFERS_FALLTHROUGH() [[clang::fallthrough]] + #elif FLATBUFFERS_GCC >= 70300 + #define FLATBUFFERS_FALLTHROUGH() [[gnu::fallthrough]] + #else + #define FLATBUFFERS_FALLTHROUGH() + #endif +#endif + +/// @endcond + +/// @file +namespace flatbuffers { + +/// @cond FLATBUFFERS_INTERNAL +// Our default offset / size type, 32bit on purpose on 64bit systems. +// Also, using a consistent offset type maintains compatibility of serialized +// offset values between 32bit and 64bit systems. +using uoffset_t = uint32_t; + +// Signed offsets for references that can go in both directions. +using soffset_t = int32_t; + +// Offset/index used in v-tables, can be changed to uint8_t in +// format forks to save a bit of space if desired. +using voffset_t = uint16_t; + +using largest_scalar_t = uintmax_t; + +// In 32bits, this evaluates to 2GB - 1 +#define FLATBUFFERS_MAX_BUFFER_SIZE ((1ULL << (sizeof(::flatbuffers::soffset_t) * 8U - 1U)) - 1U) + +// We support aligning the contents of buffers up to this size. +#define FLATBUFFERS_MAX_ALIGNMENT 16U + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4127) // C4127: conditional expression is constant +#endif + +template T EndianSwap(T t) { + #if defined(_MSC_VER) + #define FLATBUFFERS_BYTESWAP16 _byteswap_ushort + #define FLATBUFFERS_BYTESWAP32 _byteswap_ulong + #define FLATBUFFERS_BYTESWAP64 _byteswap_uint64 + #elif defined(__ICCARM__) + #define FLATBUFFERS_BYTESWAP16 __REV16 + #define FLATBUFFERS_BYTESWAP32 __REV + #define FLATBUFFERS_BYTESWAP64(x) \ + ((__REV(static_cast(x >> 32U))) | (static_cast(__REV(static_cast(x)))) << 32U) + #else + #if defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ < 408 && !defined(__clang__) + // __builtin_bswap16 was missing prior to GCC 4.8. + #define FLATBUFFERS_BYTESWAP16(x) \ + static_cast(__builtin_bswap32(static_cast(x) << 16)) + #else + #define FLATBUFFERS_BYTESWAP16 __builtin_bswap16 + #endif + #define FLATBUFFERS_BYTESWAP32 __builtin_bswap32 + #define FLATBUFFERS_BYTESWAP64 __builtin_bswap64 + #endif + if (sizeof(T) == 1U) { // Compile-time if-then's. + return t; + } else if (sizeof(T) == 2U) { + uint16_t i; + std::memcpy(&i, &t, sizeof(T)); + i = FLATBUFFERS_BYTESWAP16(i); + std::memcpy(&t, &i, sizeof(T)); + return t; + } else if (sizeof(T) == 4U) { + uint32_t i; + std::memcpy(&i, &t, sizeof(T)); + i = FLATBUFFERS_BYTESWAP32(i); + std::memcpy(&t, &i, sizeof(T)); + return t; + } else if (sizeof(T) == 8U) { + uint64_t i; + std::memcpy(&i, &t, sizeof(T)); + i = FLATBUFFERS_BYTESWAP64(i); + std::memcpy(&t, &i, sizeof(T)); + return t; + } else { + FLATBUFFERS_ASSERT(0); + return t; + } +} + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + + +template T EndianScalar(T t) { + #if FLATBUFFERS_LITTLEENDIAN + return t; + #else + return EndianSwap(t); + #endif +} + +template +// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details. +__supress_ubsan__("alignment") +T ReadScalar(const void *p) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return EndianScalar(*reinterpret_cast(p)); +} + +template +// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details. +__supress_ubsan__("alignment") +void WriteScalar(void *p, T t) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + *reinterpret_cast(p) = EndianScalar(t); +} + +template struct Offset; +template __supress_ubsan__("alignment") void WriteScalar(void *p, Offset t) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + *reinterpret_cast(p) = EndianScalar(t.o); +} + +// Computes how many bytes you'd have to pad to be able to write an +// "scalar_size" scalar if the buffer had grown to "buf_size" (downwards in +// memory). +__supress_ubsan__("unsigned-integer-overflow") +inline size_t PaddingBytes(size_t buf_size, size_t scalar_size) { + return ((~buf_size) + 1U) & (scalar_size - 1U); +} + +} // namespace flatbuffers +#endif // FLATBUFFERS_BASE_H_ diff --git a/src/flatbuffer/include/flatbuffers/flatbuffers.h b/src/flatbuffer/include/flatbuffers/flatbuffers.h new file mode 100644 index 0000000..5a517e0 --- /dev/null +++ b/src/flatbuffer/include/flatbuffers/flatbuffers.h @@ -0,0 +1,2992 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#ifndef FLATBUFFERS_H_ +#define FLATBUFFERS_H_ + +#include + +#if defined(FLATBUFFERS_NAN_DEFAULTS) +# include +#endif + +namespace flatbuffers { +// Generic 'operator==' with conditional specialisations. +// T e - new value of a scalar field. +// T def - default of scalar (is known at compile-time). +template inline bool IsTheSameAs(T e, T def) { return e == def; } + +#if defined(FLATBUFFERS_NAN_DEFAULTS) && \ + defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0) +// Like `operator==(e, def)` with weak NaN if T=(float|double). +template inline bool IsFloatTheSameAs(T e, T def) { + return (e == def) || ((def != def) && (e != e)); +} +template<> inline bool IsTheSameAs(float e, float def) { + return IsFloatTheSameAs(e, def); +} +template<> inline bool IsTheSameAs(double e, double def) { + return IsFloatTheSameAs(e, def); +} +#endif + +// Check 'v' is out of closed range [low; high]. +// Workaround for GCC warning [-Werror=type-limits]: +// comparison is always true due to limited range of data type. +template +inline bool IsOutRange(const T &v, const T &low, const T &high) { + return (v < low) || (high < v); +} + +// Check 'v' is in closed range [low; high]. +template +inline bool IsInRange(const T &v, const T &low, const T &high) { + return !IsOutRange(v, low, high); +} + +// Wrapper for uoffset_t to allow safe template specialization. +// Value is allowed to be 0 to indicate a null object (see e.g. AddOffset). +template struct Offset { + uoffset_t o; + Offset() : o(0U) {} + Offset(uoffset_t _o) : o(_o) {} + Offset Union() const { return Offset(o); } + bool IsNull() const { return !o; } +}; + +inline void EndianCheck() { + int endiantest = 1; + // If this fails, see FLATBUFFERS_LITTLEENDIAN above. + // RULECHECKER_comment(1, 2, check_reinterpret_cast_extended, "Implementations that don't use cast affect memory usage and performance degradation.", true); + FLATBUFFERS_ASSERT(*reinterpret_cast(&endiantest) == + FLATBUFFERS_LITTLEENDIAN); + (void)endiantest; +} + +template FLATBUFFERS_CONSTEXPR size_t AlignOf() { + // clang-format off + #ifdef _MSC_VER + return __alignof(T); + #else + #ifndef alignof + return __alignof__(T); + #else + return alignof(T); + #endif + #endif + // clang-format on +} + +// When we read serialized data from memory, in the case of most scalars, +// we want to just read T, but in the case of Offset, we want to actually +// perform the indirection and return a pointer. +// The template specialization below does just that. +// It is wrapped in a struct since function templates can't overload on the +// return type like this. +// The typedef is for the convenience of callers of this function +// (avoiding the need for a trailing return decltype) +template struct IndirectHelper { + using return_type = T; + using mutable_return_type = T; + // RULECHECKER_comment(1, 1, check_single_use_pod_variable, "This variable will be used multiple times later.", true); + static const size_t element_stride = sizeof(T); + static return_type Read(const uint8_t *p, uoffset_t i) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return EndianScalar((reinterpret_cast(p))[i]); + } +}; +template struct IndirectHelper> { + using return_type = const T*; + using mutable_return_type = T*; + // RULECHECKER_comment(1, 1, check_single_use_pod_variable, "This variable will be used multiple times later.", true); + static const size_t element_stride = sizeof(uoffset_t); + static return_type Read(const uint8_t *p, uoffset_t i) { + p += i * sizeof(uoffset_t); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(p + ReadScalar(p)); + } +}; +template struct IndirectHelper { + using return_type = const T*; + using mutable_return_type = T*; + // RULECHECKER_comment(1, 1, check_single_use_pod_variable, "This variable will be used multiple times later.", true); + static const size_t element_stride = sizeof(T); + static return_type Read(const uint8_t *p, uoffset_t i) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(p + i * sizeof(T)); + } +}; + +// An STL compatible iterator implementation for Vector below, effectively +// calling Get() for every element. +template struct VectorIterator { + using iterator_category = std::random_access_iterator_tag; + using value_type = IT; + using difference_type = ptrdiff_t; + using pointer = IT*; + using reference = IT&; + + VectorIterator(const uint8_t *data, uoffset_t i) + : data_(data + IndirectHelper::element_stride * i) {} + VectorIterator(const VectorIterator &other) : data_(other.data_) {} + VectorIterator() : data_(nullptr) {} + + VectorIterator &operator=(const VectorIterator &other) { + data_ = other.data_; + return *this; + } + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + VectorIterator &operator=(VectorIterator &&other) { + data_ = other.data_; + return *this; + } + #endif // !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + + bool operator==(const VectorIterator &other) const { + return data_ == other.data_; + } + + bool operator<(const VectorIterator &other) const { + return data_ < other.data_; + } + + bool operator!=(const VectorIterator &other) const { + return data_ != other.data_; + } + + difference_type operator-(const VectorIterator &other) const { + return (data_ - other.data_) / IndirectHelper::element_stride; + } + + IT operator*() const { return IndirectHelper::Read(data_, 0U); } + + IT operator->() const { return IndirectHelper::Read(data_, 0U); } + + VectorIterator &operator++() { + data_ += IndirectHelper::element_stride; + return *this; + } + + VectorIterator operator++(int) { + VectorIterator temp(data_, 0); + data_ += IndirectHelper::element_stride; + return temp; + } + + VectorIterator operator+(const uoffset_t &offset) const { + return VectorIterator(data_ + offset * IndirectHelper::element_stride, + 0); + } + + VectorIterator &operator+=(const uoffset_t &offset) { + data_ += offset * IndirectHelper::element_stride; + return *this; + } + + VectorIterator &operator--() { + data_ -= IndirectHelper::element_stride; + return *this; + } + + VectorIterator operator--(int) { + VectorIterator temp(data_, 0); + data_ -= IndirectHelper::element_stride; + return temp; + } + + VectorIterator operator-(const uoffset_t &offset) const { + return VectorIterator(data_ - offset * IndirectHelper::element_stride, + 0); + } + + VectorIterator &operator-=(const uoffset_t &offset) { + data_ -= offset * IndirectHelper::element_stride; + return *this; + } + + private: + const uint8_t *data_; +}; + +template +struct VectorReverseIterator : public std::reverse_iterator { + explicit VectorReverseIterator(Iterator iter) + : std::reverse_iterator(iter) {} + + typename Iterator::value_type operator*() const { + return *(std::reverse_iterator::current); + } + + typename Iterator::value_type operator->() const { + return *(std::reverse_iterator::current); + } +}; + +struct String; + +// This is used as a helper type for accessing vectors. +// Vector::data() assumes the vector elements start after the length field. +template class Vector { + public: + using iterator = VectorIterator::mutable_return_type>; + using const_iterator = VectorIterator::return_type>; + using reverse_iterator = VectorReverseIterator; + using const_reverse_iterator = VectorReverseIterator; + + uoffset_t size() const { return EndianScalar(length_); } + + // Deprecated: use size(). Here for backwards compatibility. +/// \cond DONT_DOCUMENT + FLATBUFFERS_ATTRIBUTE(deprecated("use size() instead")) +/// \endcond + uoffset_t Length() const { return size(); } + + using return_type = typename IndirectHelper::return_type; + using mutable_return_type = typename IndirectHelper::mutable_return_type; + + return_type Get(uoffset_t i) const { + FLATBUFFERS_ASSERT(i < size()); + return IndirectHelper::Read(Data(), i); + } + + return_type operator[](uoffset_t i) const { return Get(i); } + + // If this is a Vector of enums, T will be its storage type, not the enum + // type. This function makes it convenient to retrieve value with enum + // type E. + template E GetEnum(uoffset_t i) const { + return static_cast(Get(i)); + } + + // If this a vector of unions, this does the cast for you. There's no check + // to make sure this is the right type! + template const U *GetAs(uoffset_t i) const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(Get(i)); + } + + // If this a vector of unions, this does the cast for you. There's no check + // to make sure this is actually a string! + const String *GetAsString(uoffset_t i) const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(Get(i)); + } + + const void *GetStructFromOffset(size_t o) const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(Data() + o); + } + + iterator begin() { return iterator(Data(), 0); } + const_iterator begin() const { return const_iterator(Data(), 0); } + + iterator end() { return iterator(Data(), size()); } + const_iterator end() const { return const_iterator(Data(), size()); } + + reverse_iterator rbegin() { return reverse_iterator(end() - 1); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end() - 1); + } + +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_START("Tests for reverse iterator won't compile"); +/// \endcond + reverse_iterator rend() { return reverse_iterator(begin() - 1); } + + const_reverse_iterator rend() const { + return const_reverse_iterator(begin() - 1); + } + + const_reverse_iterator crend() const { return rend(); } +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + + const_iterator cbegin() const { return begin(); } + + const_iterator cend() const { return end(); } + + const_reverse_iterator crbegin() const { return rbegin(); } + + // Change elements if you have a non-const pointer to this object. + // Scalars only. See reflection.h, and the documentation. + void Mutate(uoffset_t i, const T &val) { + FLATBUFFERS_ASSERT(i < size()); + WriteScalar(data() + i, val); + } + + // Change an element of a vector of tables (or strings). + // "val" points to the new table/string, as you can obtain from + // e.g. reflection::AddFlatBuffer(). + void MutateOffset(uoffset_t i, const uint8_t *val) { + FLATBUFFERS_ASSERT(i < size()); + static_assert(sizeof(T) == sizeof(uoffset_t), "Unrelated types"); + WriteScalar(data() + i, + static_cast(val - (Data() + i * sizeof(uoffset_t)))); + } + + // Get a mutable pointer to tables/strings inside this vector. + mutable_return_type GetMutableObject(uoffset_t i) const { + FLATBUFFERS_ASSERT(i < size()); + return const_cast(IndirectHelper::Read(Data(), i)); + } + + // The raw data in little endian format. Use with care. + const uint8_t *Data() const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(&length_ + 1); + } + + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + uint8_t *Data() { return reinterpret_cast(&length_ + 1); } + + // Similarly, but typed, much like std::vector::data + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + const T *data() const { return reinterpret_cast(Data()); } + T *data() { return reinterpret_cast(Data()); } + +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_START("to be done"); +/// \endcond + template return_type LookupByKey(K key) const { + void *search_result = std::bsearch( + &key, Data(), size(), IndirectHelper::element_stride, KeyCompare); + + if (!search_result) { + return nullptr; // Key not found. + } + + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + const uint8_t *element = reinterpret_cast(search_result); + + return IndirectHelper::Read(element, 0); + } + + protected: + // This class is only used to access pre-existing data. Don't ever + // try to construct these manually. + Vector(); + + uoffset_t length_; + + private: + // This class is a pointer. Copying will therefore create an invalid object. + // Private and unimplemented copy constructor. + Vector(const Vector &); + Vector &operator=(const Vector &); + + template static int KeyCompare(const void *ap, const void *bp) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + const K *key = reinterpret_cast(ap); + const uint8_t *data = reinterpret_cast(bp); + auto table = IndirectHelper::Read(data, 0); + + // std::bsearch compares with the operands transposed, so we negate the + // result here. + return -table->KeyCompareWithValue(*key); + } +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond +}; + +// Represent a vector much like the template above, but in this case we +// don't know what the element types are (used with reflection.h). +class VectorOfAny { + public: + uoffset_t size() const { return EndianScalar(length_); } + + const uint8_t *Data() const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(&length_ + 1); + } + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + uint8_t *Data() { return reinterpret_cast(&length_ + 1); } + + protected: + VectorOfAny(); + + uoffset_t length_; + + private: + VectorOfAny(const VectorOfAny &); + VectorOfAny &operator=(const VectorOfAny &); +}; + +#ifndef FLATBUFFERS_CPP98_STL +template +Vector> *VectorCast(Vector> *ptr) { + static_assert(std::is_base_of::value, "Unrelated types"); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast> *>(ptr); +} + +template +const Vector> *VectorCast(const Vector> *ptr) { + static_assert(std::is_base_of::value, "Unrelated types"); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast> *>(ptr); +} +#endif + +// Convenient helper function to get the length of any vector, regardless +// of whether it is null or not (the field is not set). +template static inline size_t VectorLength(const Vector *v) { + return v ? v->size() : 0; +} + +// This is used as a helper type for accessing arrays. +template class Array { + using scalar_tag = + typename flatbuffers::integral_constant::value>; + using IndirectHelperType = + typename flatbuffers::conditional::type; + + public: + using return_type = typename IndirectHelper::return_type; + using const_iterator = VectorIterator; + using const_reverse_iterator = VectorReverseIterator; + + FLATBUFFERS_CONSTEXPR uint16_t size() const { return length; } + + return_type Get(uoffset_t i) const { + FLATBUFFERS_ASSERT(i < size()); + return IndirectHelper::Read(Data(), i); + } + + return_type operator[](uoffset_t i) const { return Get(i); } + + // If this is a Vector of enums, T will be its storage type, not the enum + // type. This function makes it convenient to retrieve value with enum + // type E. + template E GetEnum(uoffset_t i) const { + return static_cast(Get(i)); + } + + const_iterator begin() const { return const_iterator(Data(), 0); } + const_iterator end() const { return const_iterator(Data(), size()); } + + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end() - 1); + } +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_START("Tests for reverse iterator won't compile"); +/// \endcond + const_reverse_iterator rend() const { return const_reverse_iterator(begin() - 1); } + const_reverse_iterator crend() const { return rend(); } +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + + const_iterator cbegin() const { return begin(); } + const_iterator cend() const { return end(); } + + const_reverse_iterator crbegin() const { return rbegin(); } + + // Get a mutable pointer to elements inside this array. + // This method used to mutate arrays of structs followed by a @p Mutate + // operation. For primitive types use @p Mutate directly. + // @warning Assignments and reads to/from the dereferenced pointer are not + // automatically converted to the correct endianness. + typename flatbuffers::conditional::type + GetMutablePointer(uoffset_t i) const { + FLATBUFFERS_ASSERT(i < size()); + return const_cast(&data()[i]); + } + + // Change elements if you have a non-const pointer to this object. + void Mutate(uoffset_t i, const T &val) { MutateImpl(scalar_tag(), i, val); } + + // The raw data in little endian format. Use with care. + const uint8_t *Data() const { return data_; } + + uint8_t *Data() { return data_; } + + // Similarly, but typed, much like std::vector::data + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + const T *data() const { return reinterpret_cast(Data()); } + T *data() { return reinterpret_cast(Data()); } + + protected: + void MutateImpl(flatbuffers::integral_constant, uoffset_t i, + const T &val) { + FLATBUFFERS_ASSERT(i < size()); + WriteScalar(data() + i, val); + } + + void MutateImpl(flatbuffers::integral_constant, uoffset_t i, + const T &val) { + *(GetMutablePointer(i)) = val; + } + + // This class is only used to access pre-existing data. Don't ever + // try to construct these manually. + // 'constexpr' allows us to use 'size()' at compile time. + // @note Must not use 'FLATBUFFERS_CONSTEXPR' here, as const is not allowed on + // a constructor. +#if defined(__cpp_constexpr) + constexpr Array(); +#else + Array(); +#endif + + uint8_t data_[length * sizeof(T)]; + + private: + // This class is a pointer. Copying will therefore create an invalid object. + // Private and unimplemented copy constructor. + Array(const Array &); + Array &operator=(const Array &); +}; + +// Specialization for Array[struct] with access using Offset pointer. +// This specialization used by idl_gen_text.cpp. +template class Array, length> { + static_assert(flatbuffers::is_same::value, "unexpected type T"); + + public: + using return_type = const void *; + + const uint8_t *Data() const { return data_; } + + // Make idl_gen_text.cpp::PrintContainer happy. + return_type operator[](uoffset_t) const { + FLATBUFFERS_ASSERT(false); + return nullptr; + } + + private: + // This class is only used to access pre-existing data. + Array(); + Array(const Array &); + Array &operator=(const Array &); + + uint8_t data_[1]; +}; + +// Lexicographically compare two strings (possibly containing nulls), and +// return true if the first is less than the second. +// RULECHECKER_comment(1, 2, check_max_parameters, "Required by current design.", true); +static inline bool StringLessThan(const char *a_data, uoffset_t a_size, + const char *b_data, uoffset_t b_size) { + const auto cmp = memcmp(a_data, b_data, (std::min)(a_size, b_size)); + return cmp == 0 ? a_size < b_size : cmp < 0; +} + +struct String : public Vector { + // RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "Implementations that don't use cast affect memory usage and performance degradation.", true); + const char *c_str() const { return reinterpret_cast(Data()); } + std::string str() const { return std::string(c_str(), size()); } + + // clang-format off + #ifdef FLATBUFFERS_HAS_STRING_VIEW + flatbuffers::string_view string_view() const { + return flatbuffers::string_view(c_str(), size()); + } + #endif // FLATBUFFERS_HAS_STRING_VIEW + // clang-format on + + bool operator<(const String &o) const { + return StringLessThan(this->data(), this->size(), o.data(), o.size()); + } +}; + +// Convenience function to get std::string from a String returning an empty +// string on null pointer. +static inline std::string GetString(const String *str) { + return str ? str->str() : ""; +} + +// Convenience function to get char* from a String returning an empty string on +// null pointer. +static inline const char *GetCstring(const String *str) { + return str ? str->c_str() : ""; +} + +// Allocator interface. This is flatbuffers-specific and meant only for +// `vector_downward` usage. +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations not safety critical"); +/// \endcond +class Allocator { + public: + virtual ~Allocator() {} + + // Allocate `size` bytes of memory. + virtual uint8_t *allocate(size_t size) = 0; + + // Deallocate `size` bytes of memory at `p` allocated by this allocator. + virtual void deallocate(uint8_t *p, size_t size) = 0; + + // Reallocate `new_size` bytes of memory, replacing the old region of size + // `old_size` at `p`. In contrast to a normal realloc, this grows downwards, + // and is intended specifcally for `vector_downward` use. + // `in_use_back` and `in_use_front` indicate how much of `old_size` is + // actually in use at each end, and needs to be copied. + // RULECHECKER_comment(1, 3, check_max_parameters, "Not in safety critical area, required by current design.", true); + virtual uint8_t *reallocate_downward(uint8_t *old_p, size_t old_size, + size_t new_size, size_t in_use_back, + size_t in_use_front) { + FLATBUFFERS_ASSERT(new_size > old_size); // vector_downward only grows + uint8_t *new_p = allocate(new_size); + memcpy_downward(old_p, old_size, new_p, new_size, in_use_back, + in_use_front); + deallocate(old_p, old_size); + return new_p; + } + + protected: + // Called by `reallocate_downward` to copy memory from `old_p` of `old_size` + // to `new_p` of `new_size`. Only memory of size `in_use_front` and + // `in_use_back` will be copied from the front and back of the old memory + // allocation. + // RULECHECKER_comment(1, 3, check_max_parameters, "Not in safety critical area, required by current design.", true); + void memcpy_downward(uint8_t *old_p, size_t old_size, uint8_t *new_p, + size_t new_size, size_t in_use_back, + size_t in_use_front) { + memcpy(new_p + new_size - in_use_back, old_p + old_size - in_use_back, + in_use_back); + memcpy(new_p, old_p, in_use_front); + } +}; +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +// DefaultAllocator uses new/delete to allocate memory regions +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +class DefaultAllocator : public Allocator { + public: + uint8_t *allocate(size_t size) FLATBUFFERS_OVERRIDE { + // RULECHECKER_comment(1, 1, check_new_operator, "New is necessary here for memory allocation.", true); + return new uint8_t[size]; + } + + // RULECHECKER_comment(1, 1, check_delete_operator, "Delete is necessary here for deallocation.", true); + void deallocate(uint8_t *p, size_t) FLATBUFFERS_OVERRIDE { delete[] p; } + + // RULECHECKER_comment(1, 1, check_delete_operator, "Delete is necessary here for deallocation.", true); + static void dealloc(void *p, size_t) { delete[] static_cast(p); } +}; +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +// These functions allow for a null allocator to mean use the default allocator, +// as used by DetachedBuffer and vector_downward below. +// This is to avoid having a statically or dynamically allocated default +// allocator, or having to move it between the classes that may own it. +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +inline uint8_t *Allocate(Allocator *allocator, size_t size) { + return allocator ? allocator->allocate(size) + : DefaultAllocator().allocate(size); +} +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +inline void Deallocate(Allocator *allocator, uint8_t *p, size_t size) { + if (allocator) + allocator->deallocate(p, size); + else + DefaultAllocator().deallocate(p, size); +} +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +// RULECHECKER_comment(1, 3, check_max_parameters, "Not in safety critical area, required by current design.", true); +inline uint8_t *ReallocateDownward(Allocator *allocator, uint8_t *old_p, + size_t old_size, size_t new_size, + size_t in_use_back, size_t in_use_front) { + return allocator ? allocator->reallocate_downward(old_p, old_size, new_size, + in_use_back, in_use_front) + : DefaultAllocator().reallocate_downward( + old_p, old_size, new_size, in_use_back, in_use_front); +} +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +// DetachedBuffer is a finished flatbuffer memory region, detached from its +// builder. The original memory region and allocator are also stored so that +// the DetachedBuffer can manage the memory lifetime. +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +class DetachedBuffer { + public: + DetachedBuffer() + : allocator_(nullptr), + own_allocator_(false), + buf_(nullptr), + reserved_(0), + cur_(nullptr), + size_(0) {} + // RULECHECKER_comment(1, 2, check_max_parameters, "Not in safety critical area, required by current design.", true); + DetachedBuffer(Allocator *allocator, bool own_allocator, uint8_t *buf, + size_t reserved, uint8_t *cur, size_t sz) + : allocator_(allocator), + own_allocator_(own_allocator), + buf_(buf), + reserved_(reserved), + cur_(cur), + size_(sz) {} + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + DetachedBuffer(DetachedBuffer &&other) + : allocator_(other.allocator_), + own_allocator_(other.own_allocator_), + buf_(other.buf_), + reserved_(other.reserved_), + cur_(other.cur_), + size_(other.size_) { + other.reset(); + } + // clang-format off + #endif // !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + DetachedBuffer &operator=(DetachedBuffer &&other) { + if (this == &other) return *this; + + destroy(); + + allocator_ = other.allocator_; + own_allocator_ = other.own_allocator_; + buf_ = other.buf_; + reserved_ = other.reserved_; + cur_ = other.cur_; + size_ = other.size_; + + other.reset(); + + return *this; + } + // clang-format off + #endif // !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + + ~DetachedBuffer() { destroy(); } + + const uint8_t *data() const { return cur_; } + + uint8_t *data() { return cur_; } + + size_t size() const { return size_; } + + // clang-format off + #if 0 // disabled for now due to the ordering of classes in this header + template + bool Verify() const { + Verifier verifier(data(), size()); + return verifier.Verify(nullptr); + } + + template + const T* GetRoot() const { + return flatbuffers::GetRoot(data()); + } + + template + T* GetRoot() { + return flatbuffers::GetRoot(data()); + } + #endif + // clang-format on + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + // These may change access mode, leave these at end of public section +/// \cond DONT_DOCUMENT + FLATBUFFERS_DELETE_FUNC(DetachedBuffer(const DetachedBuffer &other)) +/// \endcond +/// \cond DONT_DOCUMENT + FLATBUFFERS_DELETE_FUNC( + DetachedBuffer &operator=(const DetachedBuffer &other)) + // clang-format off + #endif // !defined(FLATBUFFERS_CPP98_STL) + // clang-format on +/// \endcond + + protected: + Allocator *allocator_; + bool own_allocator_; + uint8_t *buf_; + size_t reserved_; + uint8_t *cur_; + size_t size_; + + inline void destroy() { + if (buf_) Deallocate(allocator_, buf_, reserved_); + // RULECHECKER_comment(1, 1, check_delete_operator, "Delete is necessary here for deallocation.", true); + if (own_allocator_ && allocator_) { delete allocator_; } + reset(); + } + + inline void reset() { + allocator_ = nullptr; + own_allocator_ = false; + buf_ = nullptr; + reserved_ = 0U; + cur_ = nullptr; + size_ = 0U; + } +}; +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +// This is a minimal replication of std::vector functionality, +// except growing from higher to lower addresses. i.e push_back() inserts data +// in the lowest address in the vector. +// Since this vector leaves the lower part unused, we support a "scratch-pad" +// that can be stored there for temporary data, to share the allocated space. +// Essentially, this supports 2 std::vectors in a single buffer. +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +class vector_downward { + public: + // RULECHECKER_comment(1, 2, check_max_parameters, "Not in safety critical area, required by current design.", true); + explicit vector_downward(size_t initial_size, Allocator *allocator, + bool own_allocator, size_t buffer_minalign) + : allocator_(allocator), + own_allocator_(own_allocator), + initial_size_(initial_size), + buffer_minalign_(buffer_minalign), + reserved_(0U), + buf_(nullptr), + cur_(nullptr), + scratch_(nullptr) {} + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + vector_downward(vector_downward &&other) + #else + vector_downward(vector_downward &other) + #endif // defined(FLATBUFFERS_CPP98_STL) + // clang-format on + : allocator_(other.allocator_), + own_allocator_(other.own_allocator_), + initial_size_(other.initial_size_), + buffer_minalign_(other.buffer_minalign_), + reserved_(other.reserved_), + buf_(other.buf_), + cur_(other.cur_), + scratch_(other.scratch_) { + // No change in other.allocator_ + // No change in other.initial_size_ + // No change in other.buffer_minalign_ + other.own_allocator_ = false; + other.reserved_ = 0U; + other.buf_ = nullptr; + other.cur_ = nullptr; + other.scratch_ = nullptr; + } + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + vector_downward &operator=(vector_downward &&other) { + // Move construct a temporary and swap idiom + vector_downward temp(std::move(other)); + swap(temp); + return *this; + } + // clang-format off + #endif // defined(FLATBUFFERS_CPP98_STL) + // clang-format on + + ~vector_downward() { + clear_buffer(); + clear_allocator(); + } + + void reset() { + clear_buffer(); + clear(); + } + + void clear() { + if (buf_) { + cur_ = buf_ + reserved_; + } else { + reserved_ = 0U; + cur_ = nullptr; + } + clear_scratch(); + } + + void clear_scratch() { scratch_ = buf_; } + + void clear_allocator() { + // RULECHECKER_comment(1, 1, check_delete_operator, "Delete is necessary here for deallocation.", true); + if (own_allocator_ && allocator_) { delete allocator_; } + allocator_ = nullptr; + own_allocator_ = false; + } + + void clear_buffer() { + if (buf_) Deallocate(allocator_, buf_, reserved_); + buf_ = nullptr; + } + + // Relinquish the pointer to the caller. + uint8_t *release_raw(size_t &allocated_bytes, size_t &offset) { + auto *buf = buf_; + allocated_bytes = reserved_; + offset = static_cast(cur_ - buf_); + + // release_raw only relinquishes the buffer ownership. + // Does not deallocate or reset the allocator. Destructor will do that. + buf_ = nullptr; + clear(); + return buf; + } + + // Relinquish the pointer to the caller. + DetachedBuffer release() { + // allocator ownership (if any) is transferred to DetachedBuffer. + DetachedBuffer fb(allocator_, own_allocator_, buf_, reserved_, cur_, + size()); + if (own_allocator_) { + allocator_ = nullptr; + own_allocator_ = false; + } + buf_ = nullptr; + clear(); + return fb; + } + + size_t ensure_space(size_t len) { + FLATBUFFERS_ASSERT(cur_ >= scratch_ && scratch_ >= buf_); + if (len > static_cast(cur_ - scratch_)) { reallocate(len); } + // Beyond this, signed offsets may not have enough range: + // (FlatBuffers > 2GB not supported). + FLATBUFFERS_ASSERT(size() < FLATBUFFERS_MAX_BUFFER_SIZE); + return len; + } + + inline uint8_t *make_space(size_t len) { + size_t space = ensure_space(len); + cur_ -= space; + return cur_; + } + + // Returns nullptr if using the DefaultAllocator. + Allocator *get_custom_allocator() { return allocator_; } + + uoffset_t size() const { + return static_cast(reserved_ - (cur_ - buf_)); + } + + uoffset_t scratch_size() const { + return static_cast(scratch_ - buf_); + } + + size_t capacity() const { return reserved_; } + + uint8_t *data() const { + FLATBUFFERS_ASSERT(cur_); + return cur_; + } + + uint8_t *scratch_data() const { + FLATBUFFERS_ASSERT(buf_); + return buf_; + } + + uint8_t *scratch_end() const { + FLATBUFFERS_ASSERT(scratch_); + return scratch_; + } + + uint8_t *data_at(size_t offset) const { return buf_ + reserved_ - offset; } + + void push(const uint8_t *bytes, size_t num) { + if (num > 0U) { memcpy(make_space(num), bytes, num); } + } + + // Specialized version of push() that avoids memcpy call for small data. + template void push_small(const T &little_endian_t) { + make_space(sizeof(T)); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + *reinterpret_cast(cur_) = little_endian_t; + } + + template void scratch_push_small(const T &t) { + ensure_space(sizeof(T)); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + *reinterpret_cast(scratch_) = t; + scratch_ += sizeof(T); + } + + // fill() is most frequently called with small byte counts (<= 4), + // which is why we're using loops rather than calling memset. + void fill(size_t zero_pad_bytes) { + make_space(zero_pad_bytes); + for (size_t i = 0U; i < zero_pad_bytes; i++) cur_[i] = 0U; + } + + // Version for when we know the size is larger. + // Precondition: zero_pad_bytes > 0 + void fill_big(size_t zero_pad_bytes) { + memset(make_space(zero_pad_bytes), 0, zero_pad_bytes); + } + + void pop(size_t bytes_to_remove) { cur_ += bytes_to_remove; } + void scratch_pop(size_t bytes_to_remove) { scratch_ -= bytes_to_remove; } + + void swap(vector_downward &other) { + using std::swap; + swap(allocator_, other.allocator_); + swap(own_allocator_, other.own_allocator_); + swap(initial_size_, other.initial_size_); + swap(buffer_minalign_, other.buffer_minalign_); + swap(reserved_, other.reserved_); + swap(buf_, other.buf_); + swap(cur_, other.cur_); + swap(scratch_, other.scratch_); + } + + void swap_allocator(vector_downward &other) { + using std::swap; + swap(allocator_, other.allocator_); + swap(own_allocator_, other.own_allocator_); + } + + private: + // You shouldn't really be copying instances of this class. + /// \cond DONT_DOCUMENT + FLATBUFFERS_DELETE_FUNC(vector_downward(const vector_downward &)) + /// \endcond + /// \cond DONT_DOCUMENT + FLATBUFFERS_DELETE_FUNC(vector_downward &operator=(const vector_downward &)) + /// \endcond + + Allocator *allocator_; + bool own_allocator_; + size_t initial_size_; + size_t buffer_minalign_; + size_t reserved_; + uint8_t *buf_; + uint8_t *cur_; // Points at location between empty (below) and used (above). + uint8_t *scratch_; // Points to the end of the scratchpad in use. + + void reallocate(size_t len) { + auto old_reserved = reserved_; + auto old_size = size(); + auto old_scratch_size = scratch_size(); + reserved_ += + (std::max)(len, old_reserved ? old_reserved / 2U : initial_size_); + reserved_ = (reserved_ + buffer_minalign_ - 1U) & ~(buffer_minalign_ - 1U); + if (buf_) { + buf_ = ReallocateDownward(allocator_, buf_, old_reserved, reserved_, + old_size, old_scratch_size); + } else { + buf_ = Allocate(allocator_, reserved_); + } + cur_ = buf_ + reserved_ - old_size; + scratch_ = buf_ + old_scratch_size; + } +}; +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +// Converts a Field ID to a virtual table offset. +inline voffset_t FieldIndexToOffset(voffset_t field_id) { + // Should correspond to what EndTable() below builds up. + auto fixed_fields = 2U; // Vtable size and Object Size. + return static_cast((field_id + fixed_fields) * sizeof(voffset_t)); +} + +template +const T *data(const std::vector &v) { + // Eventually the returned pointer gets passed down to memcpy, so + // we need it to be non-null to avoid undefined behavior. + static uint8_t t = 0U; + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return v.empty() ? reinterpret_cast(&t) : &v.front(); +} +template T *data(std::vector &v) { + // Eventually the returned pointer gets passed down to memcpy, so + // we need it to be non-null to avoid undefined behavior. + static uint8_t t = 0U; + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return v.empty() ? reinterpret_cast(&t) : &v.front(); +} + +/// @endcond + +/// @addtogroup flatbuffers_cpp_api +/// @{ +/// @class FlatBufferBuilder +/// @brief Helper class to hold data needed in creation of a FlatBuffer. +/// To serialize data, you typically call one of the `Create*()` functions in +/// the generated code, which in turn call a sequence of `StartTable`/ +/// `PushElement`/`AddElement`/`EndTable`, or the builtin `CreateString`/ +/// `CreateVector` functions. Do this is depth-first order to build up a tree to +/// the root. `Finish()` wraps up the buffer ready for transport. +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +class FlatBufferBuilder { + public: + /// @brief Default constructor for FlatBufferBuilder. + /// @param[in] initial_size The initial size of the buffer, in bytes. Defaults + /// to `1024`. + /// @param[in] allocator An `Allocator` to use. If null will use + /// `DefaultAllocator`. + /// @param[in] own_allocator Whether the builder/vector should own the + /// allocator. Defaults to / `false`. + /// @param[in] buffer_minalign Force the buffer to be aligned to the given + /// minimum alignment upon reallocation. Only needed if you intend to store + /// types with custom alignment AND you wish to read the buffer in-place + /// directly after creation. + // RULECHECKER_comment(1, 4, check_max_parameters, "Not in safety critical area, required by current design.", true); + explicit FlatBufferBuilder( + size_t initial_size = 1024U, Allocator *allocator = nullptr, + bool own_allocator = false, + size_t buffer_minalign = AlignOf()) + : buf_(initial_size, allocator, own_allocator, buffer_minalign), + num_field_loc(0U), + max_voffset_(0U), + nested(false), + finished(false), + minalign_(1U), + force_defaults_(false), + dedup_vtables_(true), + string_pool(nullptr) { + EndianCheck(); + } + + // clang-format off + /// @brief Move constructor for FlatBufferBuilder. + #if !defined(FLATBUFFERS_CPP98_STL) + FlatBufferBuilder(FlatBufferBuilder &&other) + #else + FlatBufferBuilder(FlatBufferBuilder &other) + #endif // #if !defined(FLATBUFFERS_CPP98_STL) + : buf_(1024U, nullptr, false, AlignOf()), + num_field_loc(0U), + max_voffset_(0U), + nested(false), + finished(false), + minalign_(1U), + force_defaults_(false), + dedup_vtables_(true), + string_pool(nullptr) { + EndianCheck(); + // Default construct and swap idiom. + // Lack of delegating constructors in vs2010 makes it more verbose than needed. + Swap(other); + } + // clang-format on + + // clang-format off + #if !defined(FLATBUFFERS_CPP98_STL) + // clang-format on + /// @brief Move assignment operator for FlatBufferBuilder. + FlatBufferBuilder &operator=(FlatBufferBuilder &&other) { + // Move construct a temporary and swap idiom + FlatBufferBuilder temp(std::move(other)); + Swap(temp); + return *this; + } + // clang-format off + #endif // defined(FLATBUFFERS_CPP98_STL) + // clang-format on + + void Swap(FlatBufferBuilder &other) { + using std::swap; + buf_.swap(other.buf_); + swap(num_field_loc, other.num_field_loc); + swap(max_voffset_, other.max_voffset_); + swap(nested, other.nested); + swap(finished, other.finished); + swap(minalign_, other.minalign_); + swap(force_defaults_, other.force_defaults_); + swap(dedup_vtables_, other.dedup_vtables_); + swap(string_pool, other.string_pool); + } + + ~FlatBufferBuilder() { + // RULECHECKER_comment(1, 1, check_delete_operator, "Delete is necessary here for deallocation.", true); + if (string_pool) delete string_pool; + } + + void Reset() { + Clear(); // clear builder state + buf_.reset(); // deallocate buffer + } + + /// @brief Reset all the state in this FlatBufferBuilder so it can be reused + /// to construct another buffer. + void Clear() { + ClearOffsets(); + buf_.clear(); + nested = false; + finished = false; + minalign_ = 1U; + if (string_pool) string_pool->clear(); + } + + /// @brief The current size of the serialized buffer, counting from the end. + /// @return Returns an `uoffset_t` with the current size of the buffer. + uoffset_t GetSize() const { return buf_.size(); } + + /// @brief Get the serialized buffer (after you call `Finish()`). + /// @return Returns an `uint8_t` pointer to the FlatBuffer data inside the + /// buffer. + uint8_t *GetBufferPointer() const { + Finished(); + return buf_.data(); + } + + /// @brief Get a pointer to an unfinished buffer. + /// @return Returns a `uint8_t` pointer to the unfinished buffer. + uint8_t *GetCurrentBufferPointer() const { return buf_.data(); } + + //SCORE Extension start + /// @brief Read a scalar value in the buffer at a given offset. + /// @return The value in the buffer. + template + T ReadScalarAtOffset(uoffset_t offset) { + return ReadScalar(GetCurrentBufferPointer() + GetSize() - offset); + } + + /// @brief Write a scalar value into the buffer at a given offset. + template + void WriteScalarAtOffset(uoffset_t offset, T value) { + WriteScalar(GetCurrentBufferPointer() + GetSize() - offset, value); + } + //SCORE Extension end + /// @brief Get the released pointer to the serialized buffer. + /// @warning Do NOT attempt to use this FlatBufferBuilder afterwards! + /// @return A `FlatBuffer` that owns the buffer and its allocator and + /// behaves similar to a `unique_ptr` with a deleter. +/// \cond DONT_DOCUMENT + FLATBUFFERS_ATTRIBUTE(deprecated("use Release() instead")) +/// \endcond + DetachedBuffer ReleaseBufferPointer() { + Finished(); + return buf_.release(); + } + + /// @brief Get the released DetachedBuffer. + /// @return A `DetachedBuffer` that owns the buffer and its allocator. + DetachedBuffer Release() { + Finished(); + return buf_.release(); + } + + /// @brief Get the released pointer to the serialized buffer. + /// @param size The size of the memory block containing + /// the serialized `FlatBuffer`. + /// @param offset The offset from the released pointer where the finished + /// `FlatBuffer` starts. + /// @return A raw pointer to the start of the memory block containing + /// the serialized `FlatBuffer`. + /// @remark If the allocator is owned, it gets deleted when the destructor is + /// called.. + uint8_t *ReleaseRaw(size_t &size, size_t &offset) { + Finished(); + return buf_.release_raw(size, offset); + } + + /// @brief get the minimum alignment this buffer needs to be accessed + /// properly. This is only known once all elements have been written (after + /// you call Finish()). You can use this information if you need to embed + /// a FlatBuffer in some other buffer, such that you can later read it + /// without first having to copy it into its own buffer. + size_t GetBufferMinAlignment() { + Finished(); + return minalign_; + } + + /// @cond FLATBUFFERS_INTERNAL + void Finished() const { + // If you get this assert, you're attempting to get access a buffer + // which hasn't been finished yet. Be sure to call + // FlatBufferBuilder::Finish with your root table. + // If you really need to access an unfinished buffer, call + // GetCurrentBufferPointer instead. + FLATBUFFERS_ASSERT(finished); + } + /// @endcond + + /// @brief In order to save space, fields that are set to their default value + /// don't get serialized into the buffer. + /// @param[in] fd When set to `true`, always serializes default values that + /// are set. Optional fields which are not set explicitly, will still not be + /// serialized. + void ForceDefaults(bool fd) { force_defaults_ = fd; } + + /// @brief By default vtables are deduped in order to save space. + /// @param[in] dedup When set to `true`, dedup vtables. + void DedupVtables(bool dedup) { dedup_vtables_ = dedup; } + + /// @cond FLATBUFFERS_INTERNAL + void Pad(size_t num_bytes) { buf_.fill(num_bytes); } + + void TrackMinAlign(size_t elem_size) { + if (elem_size > minalign_) minalign_ = elem_size; + } + + void Align(size_t elem_size) { + TrackMinAlign(elem_size); + buf_.fill(PaddingBytes(buf_.size(), elem_size)); + } + + void PushFlatBuffer(const uint8_t *bytes, size_t size) { + PushBytes(bytes, size); + finished = true; + } + + void PushBytes(const uint8_t *bytes, size_t size) { buf_.push(bytes, size); } + + void PopBytes(size_t amount) { buf_.pop(amount); } + + template void AssertScalarT() { + // The code assumes power of 2 sizes and endian-swap-ability. + static_assert(flatbuffers::is_scalar::value, "T must be a scalar type"); + } + + // Write a single aligned scalar to the buffer + template uoffset_t PushElement(T element) { + AssertScalarT(); + T litle_endian_element = EndianScalar(element); + Align(sizeof(T)); + buf_.push_small(litle_endian_element); + return GetSize(); + } + + template uoffset_t PushElement(Offset off) { + // Special case for offsets: see ReferTo below. + return PushElement(ReferTo(off.o)); + } + + // When writing fields, we track where they are, so we can create correct + // vtables later. + void TrackField(voffset_t field, uoffset_t off) { + FieldLoc fl = { off, field }; + buf_.scratch_push_small(fl); + num_field_loc++; + max_voffset_ = (std::max)(max_voffset_, field); + } + + // Like PushElement, but additionally tracks the field this represents. + template void AddElement(voffset_t field, T e, T def) { + // We don't serialize values equal to the default. + if (IsTheSameAs(e, def) && !force_defaults_) return; + auto off = PushElement(e); + TrackField(field, off); + } + + template void AddOffset(voffset_t field, Offset off) { + if (off.IsNull()) return; // Don't store. + AddElement(field, ReferTo(off.o), static_cast(0)); + } + + template void AddStruct(voffset_t field, const T *structptr) { + if (!structptr) return; // Default, don't store. + Align(AlignOf()); + buf_.push_small(*structptr); + TrackField(field, GetSize()); + } + + void AddStructOffset(voffset_t field, uoffset_t off) { + TrackField(field, off); + } + + // Offsets initially are relative to the end of the buffer (downwards). + // This function converts them to be relative to the current location + // in the buffer (when stored here), pointing upwards. + uoffset_t ReferTo(uoffset_t off) { + // Align to ensure GetSize() below is correct. + Align(sizeof(uoffset_t)); + // Offset must refer to something already in buffer. + FLATBUFFERS_ASSERT(off && off <= GetSize()); + return GetSize() - off + static_cast(sizeof(uoffset_t)); + } + + void NotNested() { + // If you hit this, you're trying to construct a Table/Vector/String + // during the construction of its parent table (between the MyTableBuilder + // and table.Finish(). + // Move the creation of these sub-objects to above the MyTableBuilder to + // not get this assert. + // Ignoring this assert may appear to work in simple cases, but the reason + // it is here is that storing objects in-line may cause vtable offsets + // to not fit anymore. It also leads to vtable duplication. + FLATBUFFERS_ASSERT(!nested); + // If you hit this, fields were added outside the scope of a table. + FLATBUFFERS_ASSERT(!num_field_loc); + } + + // From generated code (or from the parser), we call StartTable/EndTable + // with a sequence of AddElement calls in between. + uoffset_t StartTable() { + NotNested(); + nested = true; + return GetSize(); + } + + // This finishes one serialized object by generating the vtable if it's a + // table, comparing it against existing vtables, and writing the + // resulting vtable offset. + uoffset_t EndTable(uoffset_t start) { + // If you get this assert, a corresponding StartTable wasn't called. + FLATBUFFERS_ASSERT(nested); + // Write the vtable offset, which is the start of any Table. + // We fill it's value later. + auto vtableoffsetloc = PushElement(0); + // Write a vtable, which consists entirely of voffset_t elements. + // It starts with the number of offsets, followed by a type id, followed + // by the offsets themselves. In reverse: + // Include space for the last offset and ensure empty tables have a + // minimum size. + max_voffset_ = + (std::max)(static_cast(max_voffset_ + sizeof(voffset_t)), + FieldIndexToOffset(0U)); + buf_.fill_big(max_voffset_); + auto table_object_size = vtableoffsetloc - start; + // Vtable use 16bit offsets. + FLATBUFFERS_ASSERT(table_object_size < 0x10000U); + WriteScalar(buf_.data() + sizeof(voffset_t), + static_cast(table_object_size)); + WriteScalar(buf_.data(), max_voffset_); + // Write the offsets into the table + for (auto it = buf_.scratch_end() - num_field_loc * sizeof(FieldLoc); + it < buf_.scratch_end(); it += sizeof(FieldLoc)) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto field_location = reinterpret_cast(it); + auto pos = static_cast(vtableoffsetloc - field_location->off); + // If this asserts, it means you've set a field twice. + FLATBUFFERS_ASSERT( + !ReadScalar(buf_.data() + field_location->id)); + WriteScalar(buf_.data() + field_location->id, pos); + } + ClearOffsets(); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto vt1 = reinterpret_cast(buf_.data()); + auto vt1_size = ReadScalar(vt1); + auto vt_use = GetSize(); + // See if we already have generated a vtable with this exact same + // layout before. If so, make it point to the old one, remove this one. + if (dedup_vtables_) { + for (auto it = buf_.scratch_data(); it < buf_.scratch_end(); + it += sizeof(uoffset_t)) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto vt_offset_ptr = reinterpret_cast(it); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto vt2 = reinterpret_cast(buf_.data_at(*vt_offset_ptr)); + auto vt2_size = ReadScalar(vt2); + if (vt1_size != vt2_size || 0 != memcmp(vt2, vt1, vt1_size)) continue; + vt_use = *vt_offset_ptr; + buf_.pop(GetSize() - vtableoffsetloc); + break; + } + } + // If this is a new vtable, remember it. + if (vt_use == GetSize()) { buf_.scratch_push_small(vt_use); } + // Fill the vtable offset we created above. + // The offset points from the beginning of the object to where the + // vtable is stored. + // Offsets default direction is downward in memory for future format + // flexibility (storing all vtables at the start of the file). + WriteScalar(buf_.data_at(vtableoffsetloc), + static_cast(vt_use) - + static_cast(vtableoffsetloc)); + + nested = false; + return vtableoffsetloc; + } + +/// \cond DONT_DOCUMENT + FLATBUFFERS_ATTRIBUTE(deprecated("call the version above instead")) +/// \endcond + uoffset_t EndTable(uoffset_t start, voffset_t /*numfields*/) { + return EndTable(start); + } + + // This checks a required field has been set in a given table that has + // just been constructed. + template void Required(Offset table, voffset_t field); + + uoffset_t StartStruct(size_t alignment) { + Align(alignment); + return GetSize(); + } + + uoffset_t EndStruct() { return GetSize(); } + + void ClearOffsets() { + buf_.scratch_pop(num_field_loc * sizeof(FieldLoc)); + num_field_loc = 0U; + max_voffset_ = 0U; + } + + // Aligns such that when "len" bytes are written, an object can be written + // after it with "alignment" without padding. + void PreAlign(size_t len, size_t alignment) { + TrackMinAlign(alignment); + buf_.fill(PaddingBytes(GetSize() + len, alignment)); + } + template void PreAlign(size_t len) { + AssertScalarT(); + PreAlign(len, sizeof(T)); + } + /// @endcond + + /// @brief Store a string in the buffer, which can contain any binary data. + /// @param[in] str A const char pointer to the data to be stored as a string. + /// @param[in] len The number of bytes that should be stored from `str`. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateString(const char *str, size_t len) { + NotNested(); + PreAlign(len + 1U); // Always 0-terminated. + buf_.fill(1U); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + PushBytes(reinterpret_cast(str), len); + PushElement(static_cast(len)); + return Offset(GetSize()); + } + + /// @brief Store a string in the buffer, which is null-terminated. + /// @param[in] str A const char pointer to a C-string to add to the buffer. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateString(const char *str) { + return CreateString(str, strlen(str)); + } + + /// @brief Store a string in the buffer, which is null-terminated. + /// @param[in] str A char pointer to a C-string to add to the buffer. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateString(char *str) { + return CreateString(str, strlen(str)); + } + + /// @brief Store a string in the buffer, which can contain any binary data. + /// @param[in] str A const reference to a std::string to store in the buffer. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateString(const std::string &str) { + return CreateString(str.c_str(), str.length()); + } + + // clang-format off + #ifdef FLATBUFFERS_HAS_STRING_VIEW + /// @brief Store a string in the buffer, which can contain any binary data. + /// @param[in] str A const string_view to copy in to the buffer. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateString(flatbuffers::string_view str) { + return CreateString(str.data(), str.size()); + } + #endif // FLATBUFFERS_HAS_STRING_VIEW + // clang-format on + + /// @brief Store a string in the buffer, which can contain any binary data. + /// @param[in] str A const pointer to a `String` struct to add to the buffer. + /// @return Returns the offset in the buffer where the string starts + Offset CreateString(const String *str) { + return str ? CreateString(str->c_str(), str->size()) : 0; + } + + /// @brief Store a string in the buffer, which can contain any binary data. + /// @param[in] str A const reference to a std::string like type with support + /// of T::c_str() and T::length() to store in the buffer. + /// @return Returns the offset in the buffer where the string starts. + template Offset CreateString(const T &str) { + return CreateString(str.c_str(), str.length()); + } + + /// @brief Store a string in the buffer, which can contain any binary data. + /// If a string with this exact contents has already been serialized before, + /// instead simply returns the offset of the existing string. + /// @param[in] str A const char pointer to the data to be stored as a string. + /// @param[in] len The number of bytes that should be stored from `str`. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateSharedString(const char *str, size_t len) { + if (!string_pool) + // RULECHECKER_comment(1, 1, check_new_operator, "New is necessary here for allocation.", true); + string_pool = new StringOffsetMap(StringOffsetCompare(buf_)); + auto size_before_string = buf_.size(); + // Must first serialize the string, since the set is all offsets into + // buffer. + auto off = CreateString(str, len); + auto it = string_pool->find(off); + // If it exists we reuse existing serialized data! + if (it != string_pool->end()) { + // We can remove the string we serialized. + buf_.pop(buf_.size() - size_before_string); + return *it; + } + // Record this string for future use. + string_pool->insert(off); + return off; + } + + /// @brief Store a string in the buffer, which null-terminated. + /// If a string with this exact contents has already been serialized before, + /// instead simply returns the offset of the existing string. + /// @param[in] str A const char pointer to a C-string to add to the buffer. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateSharedString(const char *str) { + return CreateSharedString(str, strlen(str)); + } + + /// @brief Store a string in the buffer, which can contain any binary data. + /// If a string with this exact contents has already been serialized before, + /// instead simply returns the offset of the existing string. + /// @param[in] str A const reference to a std::string to store in the buffer. + /// @return Returns the offset in the buffer where the string starts. + Offset CreateSharedString(const std::string &str) { + return CreateSharedString(str.c_str(), str.length()); + } + + /// @brief Store a string in the buffer, which can contain any binary data. + /// If a string with this exact contents has already been serialized before, + /// instead simply returns the offset of the existing string. + /// @param[in] str A const pointer to a `String` struct to add to the buffer. + /// @return Returns the offset in the buffer where the string starts + Offset CreateSharedString(const String *str) { + return CreateSharedString(str->c_str(), str->size()); + } + + /// @cond FLATBUFFERS_INTERNAL + uoffset_t EndVector(size_t len) { + FLATBUFFERS_ASSERT(nested); // Hit if no corresponding StartVector. + nested = false; + return PushElement(static_cast(len)); + } + + void StartVector(size_t len, size_t elemsize) { + NotNested(); + nested = true; + PreAlign(len * elemsize); + PreAlign(len * elemsize, elemsize); // Just in case elemsize > uoffset_t. + } + + // Call this right before StartVector/CreateVector if you want to force the + // alignment to be something different than what the element size would + // normally dictate. + // This is useful when storing a nested_flatbuffer in a vector of bytes, + // or when storing SIMD floats, etc. + void ForceVectorAlignment(size_t len, size_t elemsize, size_t alignment) { + PreAlign(len * elemsize, alignment); + } + + // Similar to ForceVectorAlignment but for String fields. + void ForceStringAlignment(size_t len, size_t alignment) { + PreAlign((len + 1U) * sizeof(char), alignment); + } + + /// @endcond + + /// @brief Serialize an array into a FlatBuffer `vector`. + /// @tparam T The data type of the array elements. + /// @param[in] v A pointer to the array of type `T` to serialize into the + /// buffer as a `vector`. + /// @param[in] len The number of elements to serialize. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template Offset> CreateVector(const T *v, size_t len) { + // If this assert hits, you're specifying a template argument that is + // causing the wrong overload to be selected, remove it. + AssertScalarT(); + StartVector(len, sizeof(T)); + // clang-format off + #if FLATBUFFERS_LITTLEENDIAN + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + PushBytes(reinterpret_cast(v), len * sizeof(T)); + #else + if (sizeof(T) == 1) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + PushBytes(reinterpret_cast(v), len); + } else { + for (auto i = len; i > 0; ) { + PushElement(v[--i]); + } + } + #endif + // clang-format on + return Offset>(EndVector(len)); + } + + template + Offset>> CreateVector(const Offset *v, size_t len) { + StartVector(len, sizeof(Offset)); + // RULECHECKER_comment(1, 1, check_pointer_arithmetic_with_non_final, "Pointer arithmetic is necessary here for filling the vector with data.", true); + for (auto i = len; i > 0U;) { PushElement(v[--i]); } + return Offset>>(EndVector(len)); + } + + /// @brief Serialize a `std::vector` into a FlatBuffer `vector`. + /// @tparam T The data type of the `std::vector` elements. + /// @param v A const reference to the `std::vector` to serialize into the + /// buffer as a `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template Offset> CreateVector(const std::vector &v) { + return CreateVector(data(v), v.size()); + } + + // vector may be implemented using a bit-set, so we can't access it as + // an array. Instead, read elements manually. + // Background: https://isocpp.org/blog/2012/11/on-vectorbool + Offset> CreateVector(const std::vector &v) { + StartVector(v.size(), sizeof(uint8_t)); + for (auto i = v.size(); i > 0U;) { + PushElement(static_cast(v[--i])); + } + return Offset>(EndVector(v.size())); + } + + // clang-format off + #ifndef FLATBUFFERS_CPP98_STL + /// @brief Serialize values returned by a function into a FlatBuffer `vector`. + /// This is a convenience function that takes care of iteration for you. + /// @tparam T The data type of the `std::vector` elements. + /// @param f A function that takes the current iteration 0..vector_size-1 and + /// returns any type that you can construct a FlatBuffers vector out of. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template Offset> CreateVector(size_t vector_size, + const std::function &f) { + std::vector elems(vector_size); + for (size_t i = 0U; i < vector_size; i++) elems[i] = f(i); + return CreateVector(elems); + } + #endif + // clang-format on + + /// @brief Serialize values returned by a function into a FlatBuffer `vector`. + /// This is a convenience function that takes care of iteration for you. + /// @tparam T The data type of the `std::vector` elements. + /// @param f A function that takes the current iteration 0..vector_size-1, + /// and the state parameter returning any type that you can construct a + /// FlatBuffers vector out of. + /// @param state State passed to f. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVector(size_t vector_size, F f, S *state) { + std::vector elems(vector_size); + for (size_t i = 0U; i < vector_size; i++) elems[i] = f(i, state); + return CreateVector(elems); + } + + /// @brief Serialize a `std::vector` into a FlatBuffer `vector`. + /// This is a convenience function for a common case. + /// @param v A const reference to the `std::vector` to serialize into the + /// buffer as a `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + Offset>> CreateVectorOfStrings( + const std::vector &v) { + std::vector> offsets(v.size()); + for (size_t i = 0U; i < v.size(); i++) offsets[i] = CreateString(v[i]); + return CreateVector(offsets); + } + + /// @brief Serialize an array of structs into a FlatBuffer `vector`. + /// @tparam T The data type of the struct array elements. + /// @param[in] v A pointer to the array of type `T` to serialize into the + /// buffer as a `vector`. + /// @param[in] len The number of elements to serialize. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfStructs(const T *v, size_t len) { + StartVector(len * sizeof(T) / AlignOf(), AlignOf()); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + PushBytes(reinterpret_cast(v), sizeof(T) * len); + return Offset>(EndVector(len)); + } + + /// @brief Serialize an array of native structs into a FlatBuffer `vector`. + /// @tparam T The data type of the struct array elements. + /// @tparam S The data type of the native struct array elements. + /// @param[in] v A pointer to the array of type `S` to serialize into the + /// buffer as a `vector`. + /// @param[in] len The number of elements to serialize. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfNativeStructs(const S *v, + size_t len) { + extern T Pack(const S &); + std::vector vv(len); + std::transform(v, v + len, vv.begin(), Pack); + return CreateVectorOfStructs(data(vv), vv.size()); + } + + // clang-format off + #ifndef FLATBUFFERS_CPP98_STL + /// @brief Serialize an array of structs into a FlatBuffer `vector`. + /// @tparam T The data type of the struct array elements. + /// @param[in] filler A function that takes the current iteration 0..vector_size-1 + /// and a pointer to the struct that must be filled. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + /// This is mostly useful when flatbuffers are generated with mutation + /// accessors. + template Offset> CreateVectorOfStructs( + size_t vector_size, const std::function &filler) { + T* structs = StartVectorOfStructs(vector_size); + for (size_t i = 0U; i < vector_size; i++) { + filler(i, structs); + structs++; + } + return EndVectorOfStructs(vector_size); + } + #endif + // clang-format on + + /// @brief Serialize an array of structs into a FlatBuffer `vector`. + /// @tparam T The data type of the struct array elements. + /// @param[in] f A function that takes the current iteration 0..vector_size-1, + /// a pointer to the struct that must be filled and the state argument. + /// @param[in] state Arbitrary state to pass to f. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + /// This is mostly useful when flatbuffers are generated with mutation + /// accessors. + template + Offset> CreateVectorOfStructs(size_t vector_size, F f, + S *state) { + T *structs = StartVectorOfStructs(vector_size); + for (size_t i = 0U; i < vector_size; i++) { + f(i, structs, state); + structs++; + } + return EndVectorOfStructs(vector_size); + } + + /// @brief Serialize a `std::vector` of structs into a FlatBuffer `vector`. + /// @tparam T The data type of the `std::vector` struct elements. + /// @param[in] v A const reference to the `std::vector` of structs to + /// serialize into the buffer as a `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfStructs( + const std::vector &v) { + return CreateVectorOfStructs(data(v), v.size()); + } + + /// @brief Serialize a `std::vector` of native structs into a FlatBuffer + /// `vector`. + /// @tparam T The data type of the `std::vector` struct elements. + /// @tparam S The data type of the `std::vector` native struct elements. + /// @param[in] v A const reference to the `std::vector` of structs to + /// serialize into the buffer as a `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfNativeStructs( + const std::vector &v) { + return CreateVectorOfNativeStructs(data(v), v.size()); + } + + /// @cond FLATBUFFERS_INTERNAL + template struct StructKeyComparator { + bool operator()(const T &a, const T &b) const { + return a.KeyCompareLessThan(&b); + } + + private: + StructKeyComparator &operator=(const StructKeyComparator &); + }; + /// @endcond + + /// @brief Serialize a `std::vector` of structs into a FlatBuffer `vector` + /// in sorted order. + /// @tparam T The data type of the `std::vector` struct elements. + /// @param[in] v A const reference to the `std::vector` of structs to + /// serialize into the buffer as a `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfSortedStructs(std::vector *v) { + return CreateVectorOfSortedStructs(data(*v), v->size()); + } + + /// @brief Serialize a `std::vector` of native structs into a FlatBuffer + /// `vector` in sorted order. + /// @tparam T The data type of the `std::vector` struct elements. + /// @tparam S The data type of the `std::vector` native struct elements. + /// @param[in] v A const reference to the `std::vector` of structs to + /// serialize into the buffer as a `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfSortedNativeStructs( + std::vector *v) { + return CreateVectorOfSortedNativeStructs(data(*v), v->size()); + } + + /// @brief Serialize an array of structs into a FlatBuffer `vector` in sorted + /// order. + /// @tparam T The data type of the struct array elements. + /// @param[in] v A pointer to the array of type `T` to serialize into the + /// buffer as a `vector`. + /// @param[in] len The number of elements to serialize. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfSortedStructs(T *v, size_t len) { + std::sort(v, v + len, StructKeyComparator()); + return CreateVectorOfStructs(v, len); + } + + /// @brief Serialize an array of native structs into a FlatBuffer `vector` in + /// sorted order. + /// @tparam T The data type of the struct array elements. + /// @tparam S The data type of the native struct array elements. + /// @param[in] v A pointer to the array of type `S` to serialize into the + /// buffer as a `vector`. + /// @param[in] len The number of elements to serialize. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset> CreateVectorOfSortedNativeStructs(S *v, size_t len) { + extern T Pack(const S &); + std::vector vv(len); + std::transform(v, v + len, vv.begin(), Pack); + return CreateVectorOfSortedStructs(vv, len); + } + + /// @cond FLATBUFFERS_INTERNAL + template struct TableKeyComparator { + TableKeyComparator(vector_downward &buf) : buf_(buf) {} + TableKeyComparator(const TableKeyComparator &other) : buf_(other.buf_) {} + bool operator()(const Offset &a, const Offset &b) const { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto table_a = reinterpret_cast(buf_.data_at(a.o)); + auto table_b = reinterpret_cast(buf_.data_at(b.o)); + return table_a->KeyCompareLessThan(table_b); + } + vector_downward &buf_; + + private: + TableKeyComparator &operator=(const TableKeyComparator &other) { + buf_ = other.buf_; + return *this; + } + }; + /// @endcond + + /// @brief Serialize an array of `table` offsets as a `vector` in the buffer + /// in sorted order. + /// @tparam T The data type that the offset refers to. + /// @param[in] v An array of type `Offset` that contains the `table` + /// offsets to store in the buffer in sorted order. + /// @param[in] len The number of elements to store in the `vector`. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset>> CreateVectorOfSortedTables(Offset *v, + size_t len) { + std::sort(v, v + len, TableKeyComparator(buf_)); + return CreateVector(v, len); + } + + /// @brief Serialize an array of `table` offsets as a `vector` in the buffer + /// in sorted order. + /// @tparam T The data type that the offset refers to. + /// @param[in] v An array of type `Offset` that contains the `table` + /// offsets to store in the buffer in sorted order. + /// @return Returns a typed `Offset` into the serialized data indicating + /// where the vector is stored. + template + Offset>> CreateVectorOfSortedTables( + std::vector> *v) { + return CreateVectorOfSortedTables(data(*v), v->size()); + } + + /// @brief Specialized version of `CreateVector` for non-copying use cases. + /// Write the data any time later to the returned buffer pointer `buf`. + /// @param[in] len The number of elements to store in the `vector`. + /// @param[in] elemsize The size of each element in the `vector`. + /// @param[out] buf A pointer to a `uint8_t` pointer that can be + /// written to at a later time to serialize the data into a `vector` + /// in the buffer. + uoffset_t CreateUninitializedVector(size_t len, size_t elemsize, + uint8_t **buf) { + NotNested(); + StartVector(len, elemsize); + buf_.make_space(len * elemsize); + auto vec_start = GetSize(); + auto vec_end = EndVector(len); + *buf = buf_.data_at(vec_start); + return vec_end; + } + + /// @brief Specialized version of `CreateVector` for non-copying use cases. + /// Write the data any time later to the returned buffer pointer `buf`. + /// @tparam T The data type of the data that will be stored in the buffer + /// as a `vector`. + /// @param[in] len The number of elements to store in the `vector`. + /// @param[out] buf A pointer to a pointer of type `T` that can be + /// written to at a later time to serialize the data into a `vector` + /// in the buffer. + template + Offset> CreateUninitializedVector(size_t len, T **buf) { + AssertScalarT(); + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return CreateUninitializedVector(len, sizeof(T), + reinterpret_cast(buf)); + } + + template + Offset> CreateUninitializedVectorOfStructs(size_t len, + T **buf) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return CreateUninitializedVector(len, sizeof(T), + reinterpret_cast(buf)); + } + + // @brief Create a vector of scalar type T given as input a vector of scalar + // type U, useful with e.g. pre "enum class" enums, or any existing scalar + // data of the wrong type. + template + Offset> CreateVectorScalarCast(const U *v, size_t len) { + AssertScalarT(); + AssertScalarT(); + StartVector(len, sizeof(T)); + for (auto i = len; i > 0U;) { PushElement(static_cast(v[--i])); } + return Offset>(EndVector(len)); + } + + /// @brief Write a struct by itself, typically to be part of a union. + template Offset CreateStruct(const T &structobj) { + NotNested(); + Align(AlignOf()); + buf_.push_small(structobj); + return Offset(GetSize()); + } + + /// @brief The length of a FlatBuffer file header. + static const size_t kFileIdentifierLength = 4U; + + /// @brief Finish serializing a buffer by writing the root offset. + /// @param[in] file_identifier If a `file_identifier` is given, the buffer + /// will be prefixed with a standard FlatBuffers file header. + template + void Finish(Offset root, const char *file_identifier = nullptr) { + Finish(root.o, file_identifier, false); + } + + /// @brief Finish a buffer with a 32 bit size field pre-fixed (size of the + /// buffer following the size field). These buffers are NOT compatible + /// with standard buffers created by Finish, i.e. you can't call GetRoot + /// on them, you have to use GetSizePrefixedRoot instead. + /// All >32 bit quantities in this buffer will be aligned when the whole + /// size pre-fixed buffer is aligned. + /// These kinds of buffers are useful for creating a stream of FlatBuffers. + template + void FinishSizePrefixed(Offset root, + const char *file_identifier = nullptr) { + Finish(root.o, file_identifier, true); + } + + void SwapBufAllocator(FlatBufferBuilder &other) { + buf_.swap_allocator(other.buf_); + } + + protected: + // You shouldn't really be copying instances of this class. + FlatBufferBuilder(const FlatBufferBuilder &); + FlatBufferBuilder &operator=(const FlatBufferBuilder &); + + void Finish(uoffset_t root, const char *file_identifier, bool size_prefix) { + NotNested(); + buf_.clear_scratch(); + // This will cause the whole buffer to be aligned. + PreAlign((size_prefix ? sizeof(uoffset_t) : 0U) + sizeof(uoffset_t) + + (file_identifier ? kFileIdentifierLength : 0U), + minalign_); + if (file_identifier) { + FLATBUFFERS_ASSERT(strlen(file_identifier) == kFileIdentifierLength); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + PushBytes(reinterpret_cast(file_identifier), + kFileIdentifierLength); + } + PushElement(ReferTo(root)); // Location of root. + if (size_prefix) { PushElement(GetSize()); } + finished = true; + } + + struct FieldLoc { + uoffset_t off {}; + voffset_t id {}; + }; + + vector_downward buf_; + + // Accumulating offsets of table members while it is being built. + // We store these in the scratch pad of buf_, after the vtable offsets. + uoffset_t num_field_loc; + // Track how much of the vtable is in use, so we can output the most compact + // possible vtable. + voffset_t max_voffset_; + + // Ensure objects are not nested. + bool nested; + + // Ensure the buffer is finished before it is being accessed. + bool finished; + + size_t minalign_; + + bool force_defaults_; // Serialize values equal to their defaults anyway. + + bool dedup_vtables_; + + struct StringOffsetCompare { + StringOffsetCompare(const vector_downward &buf) : buf_(&buf) {} + bool operator()(const Offset &a, const Offset &b) const { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto stra = reinterpret_cast(buf_->data_at(a.o)); + auto strb = reinterpret_cast(buf_->data_at(b.o)); + return StringLessThan(stra->data(), stra->size(), strb->data(), + strb->size()); + } + const vector_downward *buf_; + }; + + // For use with CreateSharedString. Instantiated on first use only. + using StringOffsetMap = std::set, StringOffsetCompare>; + StringOffsetMap *string_pool; + + private: + // Allocates space for a vector of structures. + // Must be completed with EndVectorOfStructs(). + template T *StartVectorOfStructs(size_t vector_size) { + StartVector(vector_size * sizeof(T) / AlignOf(), AlignOf()); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(buf_.make_space(vector_size * sizeof(T))); + } + + // End the vector of structues in the flatbuffers. + // Vector should have previously be started with StartVectorOfStructs(). + template + Offset> EndVectorOfStructs(size_t vector_size) { + return Offset>(EndVector(vector_size)); + } +}; +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond +/// @} + +/// @cond FLATBUFFERS_INTERNAL +// Helpers to get a typed pointer to the root object contained in the buffer. +template T *GetMutableRoot(void *buf) { + EndianCheck(); + // RULECHECKER_comment(1, 3, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast( + reinterpret_cast(buf) + + EndianScalar(*reinterpret_cast(buf))); +} + +template const T *GetRoot(const void *buf) { + // RULECHECKER_comment(1, 1, check_pointer_qualifier_cast_const, "Constness of the pointer here is good for safety reason.", true); + return GetMutableRoot(const_cast(buf)); +} + +template const T *GetSizePrefixedRoot(const void *buf) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return GetRoot(reinterpret_cast(buf) + sizeof(uoffset_t)); +} + +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +/// Helpers to get a typed pointer to objects that are currently being built. +/// @warning Creating new objects will lead to reallocations and invalidates +/// the pointer! +template +T *GetMutableTemporaryPointer(FlatBufferBuilder &fbb, Offset offset) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(fbb.GetCurrentBufferPointer() + fbb.GetSize() - + offset.o); +} + +template +const T *GetTemporaryPointer(FlatBufferBuilder &fbb, Offset offset) { + return GetMutableTemporaryPointer(fbb, offset); +} +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +/// @brief Get a pointer to the the file_identifier section of the buffer. +/// @return Returns a const char pointer to the start of the file_identifier +/// characters in the buffer. The returned char * has length +/// 'flatbuffers::FlatBufferBuilder::kFileIdentifierLength'. +/// This function is UNDEFINED for FlatBuffers whose schema does not include +/// a file_identifier (likely points at padding or the start of a the root +/// vtable). +inline const char *GetBufferIdentifier(const void *buf, + bool size_prefixed = false) { + // RULECHECKER_comment(1, 2, check_reinterpret_cast_extended, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(buf) + + ((size_prefixed) ? 2U * sizeof(uoffset_t) : sizeof(uoffset_t)); +} + +// Helper to see if the identifier in a buffer has the expected value. +inline bool BufferHasIdentifier(const void *buf, const char *identifier, + bool size_prefixed = false) { + return strncmp(GetBufferIdentifier(buf, size_prefixed), identifier, + FlatBufferBuilder::kFileIdentifierLength) == 0; +} + +// Helper class to verify the integrity of a FlatBuffer +class Verifier FLATBUFFERS_FINAL_CLASS { + public: + // RULECHECKER_comment(1, 2, check_max_parameters, "Required by current design.", true); + Verifier(const uint8_t *buf, size_t buf_len, uoffset_t _max_depth = 64U, + uoffset_t _max_tables = 1000000U, bool _check_alignment = true) + : buf_(buf), + size_(buf_len), + depth_(0U), + max_depth_(_max_depth), + num_tables_(0U), + max_tables_(_max_tables), + upper_bound_(0U), + check_alignment_(_check_alignment) { + FLATBUFFERS_ASSERT(size_ < FLATBUFFERS_MAX_BUFFER_SIZE); + } + + // Central location where any verification failures register. + bool Check(bool ok) const { + // clang-format off + #ifdef FLATBUFFERS_DEBUG_VERIFICATION_FAILURE + FLATBUFFERS_ASSERT(ok); + #endif + #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE + if (!ok) + upper_bound_ = 0; + #endif + // clang-format on + return ok; + } + + // Verify any range within the buffer. + bool Verify(size_t elem, size_t elem_len) const { + // clang-format off + #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE + auto upper_bound = elem + elem_len; + if (upper_bound_ < upper_bound) + upper_bound_ = upper_bound; + #endif + // clang-format on + return Check(elem_len < size_ && elem <= size_ - elem_len); + } + + template bool VerifyAlignment(size_t elem) const { + return Check((elem & (sizeof(T) - 1U)) == 0U || !check_alignment_); + } + + // Verify a range indicated by sizeof(T). + template bool Verify(size_t elem) const { + return VerifyAlignment(elem) && Verify(elem, sizeof(T)); + } + + bool VerifyFromPointer(const uint8_t *p, size_t len) { + auto o = static_cast(p - buf_); + return Verify(o, len); + } + + // Verify relative to a known-good base pointer. + bool Verify(const uint8_t *base, voffset_t elem_off, size_t elem_len) const { + return Verify(static_cast(base - buf_) + elem_off, elem_len); + } + + template + bool Verify(const uint8_t *base, voffset_t elem_off) const { + return Verify(static_cast(base - buf_) + elem_off, sizeof(T)); + } + + // Verify a pointer (may be NULL) of a table type. + template bool VerifyTable(const T *table) { + return !table || table->Verify(*this); + } + + // Verify a pointer (may be NULL) of any vector type. + template bool VerifyVector(const Vector *vec) const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return !vec || VerifyVectorOrString(reinterpret_cast(vec), + sizeof(T)); + } + + // Verify a pointer (may be NULL) of a vector to struct. + template bool VerifyVector(const Vector *vec) const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return VerifyVector(reinterpret_cast *>(vec)); + } + + // Verify a pointer (may be NULL) to string. + bool VerifyString(const String *str) const { + size_t end; + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return !str || (VerifyVectorOrString(reinterpret_cast(str), + 1U, &end) && + Verify(end, 1U) && // Must have terminator + Check(buf_[end] == 0U)); // Terminating byte must be 0. + } + + // Common code between vectors and strings. + bool VerifyVectorOrString(const uint8_t *vec, size_t elem_size, + size_t *end = nullptr) const { + auto veco = static_cast(vec - buf_); + // Check we can read the size field. + if (!Verify(veco)) return false; + // Check the whole array. If this is a string, the byte past the array + // must be 0. + auto size = ReadScalar(vec); + auto max_elems = FLATBUFFERS_MAX_BUFFER_SIZE / elem_size; + if (!Check(size < max_elems)) + return false; // Protect against byte_size overflowing. + auto byte_size = sizeof(size) + elem_size * size; + if (end) *end = veco + byte_size; + return Verify(veco, byte_size); + } + + // Special case for string contents, after the above has been called. + bool VerifyVectorOfStrings(const Vector> *vec) const { + if (vec) { + for (uoffset_t i = 0U; i < vec->size(); i++) { + if (!VerifyString(vec->Get(i))) return false; + } + } + return true; + } + + // Special case for table contents, after the above has been called. + template bool VerifyVectorOfTables(const Vector> *vec) { + if (vec) { + for (uoffset_t i = 0U; i < vec->size(); i++) { + if (!vec->Get(i)->Verify(*this)) return false; + } + } + return true; + } + +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_START("to be done"); + + __supress_ubsan__("unsigned-integer-overflow") bool VerifyTableStart( + const uint8_t *table) { + // Check the vtable offset. + auto tableo = static_cast(table - buf_); + if (!Verify(tableo)) return false; + // This offset may be signed, but doing the subtraction unsigned always + // gives the result we want. + auto vtableo = tableo - static_cast(ReadScalar(table)); + // Check the vtable size field, then check vtable fits in its entirety. + return VerifyComplexity() && Verify(vtableo) && + VerifyAlignment(ReadScalar(buf_ + vtableo)) && + Verify(vtableo, ReadScalar(buf_ + vtableo)); + } +/// \endcond + + template + bool VerifyBufferFromStart(const char *identifier, size_t start) { + if (identifier && (size_ < 2U * sizeof(flatbuffers::uoffset_t) || + !BufferHasIdentifier(buf_ + start, identifier))) { + return false; + } + + // Call T::Verify, which must be in the generated code for this type. + auto o = VerifyOffset(start); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return o && reinterpret_cast(buf_ + start + o)->Verify(*this) + // clang-format off + #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE + && GetComputedSize() + #endif + ; + // clang-format on + } + + // Verify this whole buffer, starting with root type T. + template bool VerifyBuffer() { return VerifyBuffer(nullptr); } + + template bool VerifyBuffer(const char *identifier) { + return VerifyBufferFromStart(identifier, 0U); + } + + template bool VerifySizePrefixedBuffer(const char *identifier) { + return Verify(0U) && + ReadScalar(buf_) == size_ - sizeof(uoffset_t) && + VerifyBufferFromStart(identifier, sizeof(uoffset_t)); + } + + uoffset_t VerifyOffset(size_t start) const { + if (!Verify(start)) return 0U; + //SCORE Extension start + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + uoffset_t o = ReadScalar(buf_ + start); + // May not point to itself. + if (!Check(o != 0U)) return 0U; + //SCORE Extension end + // Must be inside the buffer to create a pointer from it (pointer outside + // buffer is UB). + if (!Verify(start + o, 1U)) return 0U; + return o; + } + + uoffset_t VerifyOffset(const uint8_t *base, voffset_t start) const { + return VerifyOffset(static_cast(base - buf_) + start); + } +/// \cond DONT_DOCUMENT + FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + + // Called at the start of a table to increase counters measuring data + // structure depth and amount, and possibly bails out with false if + // limits set by the constructor have been hit. Needs to be balanced + // with EndTable(). + bool VerifyComplexity() { + depth_++; + num_tables_++; + return Check(depth_ <= max_depth_ && num_tables_ <= max_tables_); + } + + // Called at the end of a table to pop the depth count. + bool EndTable() { + depth_--; + return true; + } + + // Returns the message size in bytes + size_t GetComputedSize() const { + // clang-format off + #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE + uintptr_t size = upper_bound_; + // Align the size to uoffset_t + size = (size - 1 + sizeof(uoffset_t)) & ~(sizeof(uoffset_t) - 1); + return (size > size_) ? 0 : size; + #else + // Must turn on FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE for this to work. + (void)upper_bound_; + FLATBUFFERS_ASSERT(false); + return 0U; + #endif + // clang-format on + } + + private: + const uint8_t *buf_; + size_t size_; + uoffset_t depth_; + uoffset_t max_depth_; + uoffset_t num_tables_; + uoffset_t max_tables_; + mutable size_t upper_bound_; + bool check_alignment_; +}; + +// Convenient way to bundle a buffer and its length, to pass it around +// typed by its root. +// A BufferRef does not own its buffer. +struct BufferRefBase {}; // for std::is_base_of +template struct BufferRef : BufferRefBase { + BufferRef() : buf(nullptr), len(0U), must_free(false) {} + BufferRef(uint8_t *_buf, uoffset_t _len) + : buf(_buf), len(_len), must_free(false) {} + + ~BufferRef() { + // RULECHECKER_comment(1, 1, check_stdlib_use_alloc, "Free is necessary here for deallocating the previously allocated memory.", true); + if (must_free) free(buf); + } + + const T *GetRoot() const { return flatbuffers::GetRoot(buf); } + + bool Verify() { + Verifier verifier(buf, len); + return verifier.VerifyBuffer(nullptr); + } + + uint8_t *buf; + uoffset_t len; + bool must_free; +}; + +// "structs" are flat structures that do not have an offset table, thus +// always have all members present and do not support forwards/backwards +// compatible extensions. + +class Struct FLATBUFFERS_FINAL_CLASS { + public: + template T GetField(uoffset_t o) const { + return ReadScalar(&data_[o]); + } + + template T GetStruct(uoffset_t o) const { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(&data_[o]); + } + + const uint8_t *GetAddressOf(uoffset_t o) const { return &data_[o]; } + uint8_t *GetAddressOf(uoffset_t o) { return &data_[o]; } + + private: + // private constructor & copy constructor: you obtain instances of this + // class by pointing to existing data only + Struct(); + Struct(const Struct &); + Struct &operator=(const Struct &); + + uint8_t data_[1]; +}; + +// "tables" use an offset table (possibly shared) that allows fields to be +// omitted and added at will, but uses an extra indirection to read. +class Table { + public: + const uint8_t *GetVTable() const { + return data_ - ReadScalar(data_); + } + + // This gets the field offset for any of the functions below it, or 0 + // if the field was not present. + voffset_t GetOptionalFieldOffset(voffset_t field) const { + // The vtable offset is always at the start. + auto vtable = GetVTable(); + // The first element is the size of the vtable (fields + type id + itself). + auto vtsize = ReadScalar(vtable); + // If the field we're accessing is outside the vtable, we're reading older + // data, so it's the same as if the offset was 0 (not present). + return field < vtsize ? ReadScalar(vtable + field) : 0U; + } + + template T GetField(voffset_t field, T defaultval) const { + auto field_offset = GetOptionalFieldOffset(field); + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + return field_offset ? ReadScalar(data_ + field_offset) : defaultval; + } + + template P GetPointer(voffset_t field) { + auto field_offset = GetOptionalFieldOffset(field); + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + auto p = data_ + field_offset; + //SCORE Extension start + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return field_offset ? reinterpret_cast

(p + ReadScalar(p)) + : nullptr; + //SCORE Extension end + } + template P GetPointer(voffset_t field) const { + // RULECHECKER_comment(1, 1, check_pointer_qualifier_cast_const, "Constness of the pointer here is good for safety reason.", true); + return const_cast(this)->GetPointer

(field); + } + + template P GetStruct(voffset_t field) const { + auto field_offset = GetOptionalFieldOffset(field); + // RULECHECKER_comment(2, 1, check_pointer_qualifier_cast_const, "Constness of the pointer here is good for safety reason.", true); + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + auto p = const_cast(data_ + field_offset); + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return field_offset ? reinterpret_cast

(p) : nullptr; + } + + template bool SetField(voffset_t field, T val, T def) { + auto field_offset = GetOptionalFieldOffset(field); + if (!field_offset) return IsTheSameAs(val, def); + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + WriteScalar(data_ + field_offset, val); + return true; + } + + bool SetPointer(voffset_t field, const uint8_t *val) { + auto field_offset = GetOptionalFieldOffset(field); + if (!field_offset) return false; + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + WriteScalar(data_ + field_offset, + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + static_cast(val - (data_ + field_offset))); + return true; + } + + uint8_t *GetAddressOf(voffset_t field) { + auto field_offset = GetOptionalFieldOffset(field); + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "False positive, all variables are unsigned in this case.", true); + return field_offset ? data_ + field_offset : nullptr; + } + const uint8_t *GetAddressOf(voffset_t field) const { + // RULECHECKER_comment(1, 1, check_pointer_qualifier_cast_const, "Constness of the pointer here is good for safety reason.", true); + return const_cast

(this)->GetAddressOf(field); + } + + bool CheckField(voffset_t field) const { + return GetOptionalFieldOffset(field) != 0U; + } + + // Verify the vtable of this table. + // Call this once per table, followed by VerifyField once per field. + bool VerifyTableStart(Verifier &verifier) const { + return verifier.VerifyTableStart(data_); + } + + // Verify a particular field. + template + bool VerifyField(const Verifier &verifier, voffset_t field) const { + // Calling GetOptionalFieldOffset should be safe now thanks to + // VerifyTable(). + auto field_offset = GetOptionalFieldOffset(field); + // Check the actual field. + return !field_offset || verifier.Verify(data_, field_offset); + } + + // VerifyField for required fields. + template + bool VerifyFieldRequired(const Verifier &verifier, voffset_t field) const { + auto field_offset = GetOptionalFieldOffset(field); + return verifier.Check(field_offset != 0U) && + verifier.Verify(data_, field_offset); + } + + // Versions for offsets. + bool VerifyOffset(const Verifier &verifier, voffset_t field) const { + auto field_offset = GetOptionalFieldOffset(field); + return !field_offset || verifier.VerifyOffset(data_, field_offset); + } + + bool VerifyOffsetRequired(const Verifier &verifier, voffset_t field) const { + auto field_offset = GetOptionalFieldOffset(field); + return verifier.Check(field_offset != 0U) && + verifier.VerifyOffset(data_, field_offset); + } + + private: + // private constructor & copy constructor: you obtain instances of this + // class by pointing to existing data only + Table(); + Table(const Table &other); + Table &operator=(const Table &); + + uint8_t data_[1]; +}; + +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_START("Allocations and builders not safety critical"); +/// \endcond +template +void FlatBufferBuilder::Required(Offset table, voffset_t field) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto table_ptr = reinterpret_cast(buf_.data_at(table.o)); + bool ok = table_ptr->GetOptionalFieldOffset(field) != 0U; + // If this fails, the caller will show what field needs to be set. + FLATBUFFERS_ASSERT(ok); + (void)ok; +} +/// \cond DONT_DOCUMENT +FLATBUFFERS_EXCLUDE_COVERAGE_END(); +/// \endcond + +/// @brief This can compute the start of a FlatBuffer from a root pointer, i.e. +/// it is the opposite transformation of GetRoot(). +/// This may be useful if you want to pass on a root and have the recipient +/// delete the buffer afterwards. +inline const uint8_t *GetBufferStartFromRootPointer(const void *root) { + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto table = reinterpret_cast(root); + auto vtable = table->GetVTable(); + // Either the vtable is before the root or after the root. + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + auto start = (std::min)(vtable, reinterpret_cast(root)); + // Align to at least sizeof(uoffset_t). + // RULECHECKER_comment(2, 2, check_reinterpret_cast_extended, "Implementations that don't use cast affect memory usage and performance degradation.", true); + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + start = reinterpret_cast(reinterpret_cast(start) & + ~(sizeof(uoffset_t) - 1U)); + // Additionally, there may be a file_identifier in the buffer, and the root + // offset. The buffer may have been aligned to any size between + // sizeof(uoffset_t) and FLATBUFFERS_MAX_ALIGNMENT (see "force_align"). + // Sadly, the exact alignment is only known when constructing the buffer, + // since it depends on the presence of values with said alignment properties. + // So instead, we simply look at the next uoffset_t values (root, + // file_identifier, and alignment padding) to see which points to the root. + // None of the other values can "impersonate" the root since they will either + // be 0 or four ASCII characters. + static_assert(FlatBufferBuilder::kFileIdentifierLength == sizeof(uoffset_t), + "file_identifier is assumed to be the same size as uoffset_t"); + for (auto possible_roots = FLATBUFFERS_MAX_ALIGNMENT / sizeof(uoffset_t) + 1U; + possible_roots; possible_roots--) { + start -= sizeof(uoffset_t); + // RULECHECKER_comment(1, 2, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + if (ReadScalar(start) + start == + reinterpret_cast(root)) + return start; + } + // We didn't find the root, either the "root" passed isn't really a root, + // or the buffer is corrupt. + // Assert, because calling this function with bad data may cause reads + // outside of buffer boundaries. + FLATBUFFERS_ASSERT(false); + return nullptr; +} + +/// @brief This return the prefixed size of a FlatBuffer. +inline uoffset_t GetPrefixedSize(const uint8_t *buf) { + return ReadScalar(buf); +} + +// Base class for native objects (FlatBuffer data de-serialized into native +// C++ data structures). +// Contains no functionality, purely documentative. +struct NativeTable {}; + +/// @brief Function types to be used with resolving hashes into objects and +/// back again. The resolver gets a pointer to a field inside an object API +/// object that is of the type specified in the schema using the attribute +/// `cpp_type` (it is thus important whatever you write to this address +/// matches that type). The value of this field is initially null, so you +/// may choose to implement a delayed binding lookup using this function +/// if you wish. The resolver does the opposite lookup, for when the object +/// is being serialized again. +using hash_value_t = uint64_t; +// clang-format off +#ifdef FLATBUFFERS_CPP98_STL + using resolver_function_t = void (*)(void **pointer_adr, hash_value_t hash); + using rehasher_function_t = hash_value_t (*)(void *pointer); +#else + using resolver_function_t = std::function; + using rehasher_function_t = std::function; +#endif +// clang-format on + +// Helper function to test if a field is present, using any of the field +// enums in the generated code. +// `table` must be a generated table type. Since this is a template parameter, +// this is not typechecked to be a subclass of Table, so beware! +// Note: this function will return false for fields equal to the default +// value, since they're not stored in the buffer (unless force_defaults was +// used). +template +bool IsFieldPresent(const T *table, typename T::FlatBuffersVTableOffset field) { + // Cast, since Table is a private baseclass of any table types. + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "Implementations that don't use cast affect memory usage and performance degradation.", true); + return reinterpret_cast(table)->CheckField( + static_cast(field)); +} + +// Utility function for reverse lookups on the EnumNames*() functions +// (in the generated C++ code) +// names must be NULL terminated. +inline int LookupEnum(const char * const *names, const char *name) { + for (const char * const *p = names; *p; p++) + if (!strcmp(*p, name)) return static_cast(p - names); + return -1; +} + +// These macros allow us to layout a struct with a guarantee that they'll end +// up looking the same on different compilers and platforms. +// It does this by disallowing the compiler to do any padding, and then +// does padding itself by inserting extra padding fields that make every +// element aligned to its own size. +// Additionally, it manually sets the alignment of the struct as a whole, +// which is typically its largest element, or a custom size set in the schema +// by the force_align attribute. +// These are used in the generated code only. + +// clang-format off +#if defined(_MSC_VER) + #define FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(alignment) \ + __pragma(pack(1)) \ + struct __declspec(align(alignment)) + #define FLATBUFFERS_STRUCT_END(name, size) \ + __pragma(pack()) \ + static_assert(sizeof(name) == size, "compiler breaks packing rules") +#elif defined(__GNUC__) || defined(__clang__) || defined(__ICCARM__) + #define FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(alignment) \ + _Pragma("pack(1)") \ + struct __attribute__((aligned(alignment))) + #define FLATBUFFERS_STRUCT_END(name, size) \ + _Pragma("pack()") \ + static_assert(sizeof(name) == size, "compiler breaks packing rules") +#else + #error Unknown compiler, please define structure alignment macros +#endif +// clang-format on + +// Minimal reflection via code generation. +// Besides full-fat reflection (see reflection.h) and parsing/printing by +// loading schemas (see idl.h), we can also have code generation for mimimal +// reflection data which allows pretty-printing and other uses without needing +// a schema or a parser. +// Generate code with --reflect-types (types only) or --reflect-names (names +// also) to enable. +// See minireflect.h for utilities using this functionality. + +// These types are organized slightly differently as the ones in idl.h. +enum class SequenceType { ST_TABLE, ST_STRUCT, ST_UNION, ST_ENUM }; + +// Scalars have the same order as in idl.h +// clang-format off +#define FLATBUFFERS_GEN_ELEMENTARY_TYPES(ET) \ + ET(ET_UTYPE) \ + ET(ET_BOOL) \ + ET(ET_CHAR) \ + ET(ET_UCHAR) \ + ET(ET_SHORT) \ + ET(ET_USHORT) \ + ET(ET_INT) \ + ET(ET_UINT) \ + ET(ET_LONG) \ + ET(ET_ULONG) \ + ET(ET_FLOAT) \ + ET(ET_DOUBLE) \ + ET(ET_STRING) \ + ET(ET_SEQUENCE) // See SequenceType. + +enum class ElementaryType { +/// \cond DONT_DOCUMENT + #define FLATBUFFERS_ET(E) E, + FLATBUFFERS_GEN_ELEMENTARY_TYPES(FLATBUFFERS_ET) +// RULECHECKER_comment(1, 1, check_undef, "This is required by design", true); + #undef FLATBUFFERS_ET +/// \endcond +}; + +inline const char * const *ElementaryTypeNames() { + static const char * const names[] = { +/// \cond DONT_DOCUMENT + #define FLATBUFFERS_ET(E) #E, + FLATBUFFERS_GEN_ELEMENTARY_TYPES(FLATBUFFERS_ET) +// RULECHECKER_comment(1, 1, check_undef, "This is required by design", true); + #undef FLATBUFFERS_ET +/// \endcond + }; + return names; +} +// clang-format on + +// Basic type info cost just 16bits per field! +// RULECHECKER_comment(1, 5, check_incomplete_data_member_construction, "Changing the below structure would be complicated.", true); +struct TypeCode { + uint16_t base_type : 4; // ElementaryType + uint16_t is_vector : 1; + int16_t sequence_ref : 11; // Index into type_refs below, or -1 for none. +}; + +static_assert(sizeof(TypeCode) == 2U, "TypeCode"); + +struct TypeTable; + +// Signature of the static method present in each type. +using TypeFunction = const TypeTable *(*)(); + +struct TypeTable { + SequenceType st {}; + size_t num_elems {}; // of type_codes, values, names (but not type_refs). + const TypeCode *type_codes {}; // num_elems count + const TypeFunction *type_refs {}; // less than num_elems entries (see TypeCode). + const int64_t *values {}; // Only set for non-consecutive enum/union or structs. + const char *const *names {}; // Only set if compiled with --reflect-names. +}; + +// String which identifies the current version of FlatBuffers. +// flatbuffer_version_string is used by Google developers to identify which +// applications uploaded to Google Play are using this library. This allows +// the development team at Google to determine the popularity of the library. +// How it works: Applications that are uploaded to the Google Play Store are +// scanned for this version string. We track which applications are using it +// to measure popularity. You are free to remove it (of course) but we would +// appreciate if you left it in. + +// Weak linkage is culled by VS & doesn't work on cygwin. +// clang-format off +#if !defined(_WIN32) && !defined(__CYGWIN__) + +/// \cond DONT_DOCUMENT +extern volatile __attribute__((weak)) const char *flatbuffer_version_string; +// RULECHECKER_comment(1, 10, check_single_use_pod_variable, "This single-use variable is necessary here.", true); +volatile __attribute__((weak)) const char *flatbuffer_version_string = + "FlatBuffers " + FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MAJOR) "." + FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MINOR) "." + //SCORE Extension start + FLATBUFFERS_STRING(FLATBUFFERS_VERSION_REVISION) "-" + FLATBUFFERS_STRING(score) "-" + FLATBUFFERS_STRING(FLATBUFFERS_VERSION_SCORE_MAJOR) "." + FLATBUFFERS_STRING(FLATBUFFERS_VERSION_SCORE_MINOR); + //SCORE Extension end + +#endif // !defined(_WIN32) && !defined(__CYGWIN__) +/// \endcond + +#define FLATBUFFERS_DEFINE_BITMASK_OPERATORS(E, T)\ + inline E operator | (E lhs, E rhs){\ + return E(T(lhs) | T(rhs));\ + }\ + inline E operator & (E lhs, E rhs){\ + return E(T(lhs) & T(rhs));\ + }\ + inline E operator ^ (E lhs, E rhs){\ + return E(T(lhs) ^ T(rhs));\ + }\ + inline E operator ~ (E lhs){\ + return E(~T(lhs));\ + }\ + inline E operator |= (E &lhs, E rhs){\ + lhs = lhs | rhs;\ + return lhs;\ + }\ + inline E operator &= (E &lhs, E rhs){\ + lhs = lhs & rhs;\ + return lhs;\ + }\ + inline E operator ^= (E &lhs, E rhs){\ + lhs = lhs ^ rhs;\ + return lhs;\ + }\ + inline bool operator !(E rhs) \ + {\ + return !bool(T(rhs)); \ + } +/// @endcond +} // namespace flatbuffers + +// clang-format on + +#endif // FLATBUFFERS_H_ diff --git a/src/flatbuffer/include/flatbuffers/score.h b/src/flatbuffer/include/flatbuffers/score.h new file mode 100644 index 0000000..eb64d54 --- /dev/null +++ b/src/flatbuffer/include/flatbuffers/score.h @@ -0,0 +1,42 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATBUFFERS_SCORE_H_ +#define FLATBUFFERS_SCORE_H_ + +/// @note This file is intentionally not available through flatbuffers.h to +/// maintain separation between standard flatbuffers and our extensions. + +#include + +namespace flatbuffers { +namespace score { + +/// @brief Base class for functional cluster info to be used via CRTP to generate +/// derived classes. +/// @note This is the closest we can get to an abstract base class when our +/// functions are static constexpr. +template +struct FcBase { + + /// @brief Major version of functional cluster. + static constexpr std::int32_t versionMajor() noexcept { + return T::_versionMajor(); + } + + /// @brief Minor version of functional cluster. + static constexpr std::int32_t versionMinor() noexcept { + return T::_versionMinor(); + } + + /// @brief Name of functional cluster (identical to derived type's name). + static constexpr const char * name() noexcept { + return T::_name(); + } +}; + +} // namespace score +} // namespace flatbuffers + +#endif // FLATBUFFERS_SCORE_H_ diff --git a/src/flatbuffer/include/flatbuffers/stl_emulation.h b/src/flatbuffer/include/flatbuffers/stl_emulation.h new file mode 100644 index 0000000..f3c580f --- /dev/null +++ b/src/flatbuffer/include/flatbuffers/stl_emulation.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Google Inc. All rights reserved. + * + * 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. + */ + +#ifndef FLATBUFFERS_STL_EMULATION_H_ +#define FLATBUFFERS_STL_EMULATION_H_ + +#include +#include +#include + +namespace flatbuffers { + +template +using numeric_limits = std::numeric_limits; +template using is_scalar = std::is_scalar; +template using is_same = std::is_same; +template using is_floating_point = std::is_floating_point; +template using is_unsigned = std::is_unsigned; +template using is_enum = std::is_enum; +template using make_unsigned = std::make_unsigned; +template +using conditional = std::conditional; +template +using integral_constant = std::integral_constant; +template using unique_ptr = std::unique_ptr; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_STL_EMULATION_H_ diff --git a/src/flatcfg/BUILD b/src/flatcfg/BUILD new file mode 100644 index 0000000..5448af8 --- /dev/null +++ b/src/flatcfg/BUILD @@ -0,0 +1,22 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "flatcfg", + srcs = glob([ + "src/**/*.cpp", # Ensure these are correct; consider 'src/*.cpp' if all are in 'src/' + "src/**/*.h", + ]), + hdrs = glob([ + "include/**/*.h", # Glob for other headers in current package/subdirs + ]), + strip_include_prefix = "include", + deps = [ + "//src/common", + "//src/flatbuffer:flatbuffer", + "@flatbuffers//:flatbuffers", + #"@score_baselibs//score/mw/log", + "@score_baselibs//score/result", + ], + copts = ["-iquote src/flatcfg/src"], + visibility = ["//visibility:public"], +) diff --git a/src/flatcfg/include/flatcfg/_export.h b/src/flatcfg/include/flatcfg/_export.h new file mode 100644 index 0000000..ca6050f --- /dev/null +++ b/src/flatcfg/include/flatcfg/_export.h @@ -0,0 +1,45 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_FLATCFG_EXPORT_H +#define SCORE_FLATCFG_EXPORT_H + +#ifdef SCORE_FLATCFG_STATIC_DEFINE +# define SCORE_FLATCFG_EXPORT +# define SCORE_FLATCFG_NO_EXPORT +#else +# ifndef SCORE_FLATCFG_EXPORT +# ifdef SCORE_FLATcfg_lib_EXPORTS + /* We are building this library */ +# define SCORE_FLATCFG_EXPORT __attribute__((visibility("default"))) +# else + /* We are using this library */ +# define SCORE_FLATCFG_EXPORT __attribute__((visibility("default"))) +# endif +# endif + +# ifndef SCORE_FLATCFG_NO_EXPORT +# define SCORE_FLATCFG_NO_EXPORT __attribute__((visibility("hidden"))) +# endif +#endif + +#ifndef SCORE_FLATCFG_DEPRECATED +# define SCORE_FLATCFG_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef SCORE_FLATCFG_DEPRECATED_EXPORT +# define SCORE_FLATCFG_DEPRECATED_EXPORT SCORE_FLATCFG_EXPORT SCORE_FLATCFG_DEPRECATED +#endif + +#ifndef SCORE_FLATCFG_DEPRECATED_NO_EXPORT +# define SCORE_FLATCFG_DEPRECATED_NO_EXPORT SCORE_FLATCFG_NO_EXPORT SCORE_FLATCFG_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef SCORE_FLATCFG_NO_DEPRECATED +# define SCORE_FLATCFG_NO_DEPRECATED +# endif +#endif + +#endif /* SCORE_FLATCFG_EXPORT_H */ diff --git a/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator.h b/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator.h new file mode 100644 index 0000000..e21bf14 --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator.h @@ -0,0 +1,145 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_H +#define FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_H + +#include "daemon_iterator_single.h" + +#include +#include +#include +#include + +#include "score/result/result.h" + +#include +#include + +namespace flatcfg +{ +namespace config +{ + +/// @cond Forward declaration so that friend can find the right type. +class DaemonIterator; +/// @endcond + +namespace _impl +{ + +/// @brief Iterator to iterate through the config files relevant to a daemon. +/// @note This type does not have the single directory fallback. +class DaemonIteratorImpl +{ + // friend DaemonIterator so it can construct this type directly for better + // initialization of variant + // RULECHECKER_comment(1, 1, check_friend_declaration, "Necessary for desired functionality.", true) + friend class config::DaemonIterator; + +public: + // iterator member typedefs + using difference_type = std::ptrdiff_t; + using value_type = score::Result; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::input_iterator_tag; + + /// @brief Create a daemon iterator which will iterate over relevant + /// configuration files in implicitly known daemon lookup directories. + static score::Result + FromInfo(const LookupInfo& lookupInfo) noexcept; + + /// @brief Default constructor produces a sentinel iterator, equivalent to end(). + DaemonIteratorImpl() noexcept; + + /// @brief Move constructible. + /// @note The moved-from iterator will become a sentinel iterator. + // RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "All data members are constructed.", false) + DaemonIteratorImpl(DaemonIteratorImpl&&) = default; + + /// @brief Move assignable. + /// @note The moved-from iterator will become a sentinel iterator. + DaemonIteratorImpl& + operator=(DaemonIteratorImpl&&) = default; + + /// @brief Progress to the next relevant configuration, or become a sentinel + /// iterator if there are no more relevant configurations. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + DaemonIteratorImpl& + operator++() noexcept; + + /// @brief Progress to the next relevant configuration, or become a sentinel + /// iterator if there are no more relevant configurations. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + void + operator++(int) noexcept; + + /// @brief Attempt to provide the current relevant configuration. + /// @note Any error encountered after construction is surfaced here. + /// @warning It is undefined behaviour to call this on a sentinel iterator. + value_type + operator*() const noexcept; + + /// @brief True when called on an iterator in an error state. + bool + isError() const noexcept; + + /// @brief True when called on a sentinel iterator. + bool + isSentinel() const noexcept; + + /// @brief Is equivalent to !(isError() || isSentinel()). + // RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) + operator bool() const noexcept; + + /// @brief Equality comparison. + friend bool + operator==(const DaemonIteratorImpl& lhs, const DaemonIteratorImpl& rhs) noexcept; + + /// @brief Inequality comparison. + friend bool + operator!=(const DaemonIteratorImpl& lhs, const DaemonIteratorImpl& rhs) noexcept; + + /// @brief Equality comparison with sentinel. + friend bool + operator==(const DaemonIteratorImpl& lhs, Sentinel rhs) noexcept; + + /// @brief Inequality comparison with sentinel. + friend bool + operator!=(const DaemonIteratorImpl& lhs, Sentinel rhs) noexcept; + + /// @brief Reversed equality comparison with sentinel. + friend bool + operator==(Sentinel lhs, const DaemonIteratorImpl& rhs) noexcept; + + /// @brief Reversed inequality comparison with sentinel. + friend bool + operator!=(Sentinel lhs, const DaemonIteratorImpl& rhs) noexcept; + +private: + /// @brief Create a daemon iterator which will iterate over relevant + /// configuration files in implicitly known daemon lookup directories. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + DaemonIteratorImpl(const LookupInfo& lookupInfo, score::ResultBlank *resOut) noexcept; + + /// @brief Underlying core implementation. + DaemonIteratorSingle m_single {}; + + /// @brief Extra information to check if found files are relevant. + LookupInfo m_info; +}; + +} // namespace _impl +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_H diff --git a/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator_single.h b/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator_single.h new file mode 100644 index 0000000..484024b --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator_single.h @@ -0,0 +1,80 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_SINGLE_H +#define FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_SINGLE_H + +#include "daemon_iterator_triple.h" + +#include + +#include "score/result/result.h" + +#include + +namespace flatcfg +{ +namespace config +{ +namespace _impl +{ + +/// @brief Wrapper around triple implementation that combines all underlying +/// iterators so that they can be advanced together. +/// @note Does not function as an iterator. +class DaemonIteratorSingle +{ +public: + /// @brief Create a daemon iterator single. + /// @post Initializes underlying iterators to point to the first entry if + /// it exists, a sentinel state if no entries exist, or an error + /// state if an error occurs. + DaemonIteratorSingle() noexcept; + + /// @brief Move constructible. + /// @note The moved-from instance will be put in a sentinel state. + DaemonIteratorSingle(DaemonIteratorSingle&&) = default; + + /// @brief Move assignable. + /// @note The moved-from instance will be put in a sentinel state. + DaemonIteratorSingle& + operator=(DaemonIteratorSingle&&) = default; + + /// @brief True if in an error state. + bool + isError() const noexcept; + + /// @brief True if in a sentinel state. + bool + isSentinel() const noexcept; + + /// @brief Is equivalent to !(isError() || isSentinel()). + // RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) + operator bool() const noexcept; + + /// @brief Get a view to the complete path of the current entry. + /// @pre operator bool() + std::string_view + path() const noexcept; + + /// @brief Get the current error state. + score::ResultBlank + getError() const noexcept; + + /// @brief Increment the underlying iterators to the next entry. + /// @pre operator bool() + /// @returns The value of operator bool() after calling this function. + bool + advanceEntry() noexcept; + +private: + /// @brief Underlying triple instance. + DaemonIteratorTriple m_triple {}; +}; + +} // namespace _impl +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_SINGLE_H diff --git a/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator_triple.h b/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator_triple.h new file mode 100644 index 0000000..b2ee774 --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/_impl/daemon_iterator_triple.h @@ -0,0 +1,157 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_TRIPLE_H +#define FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_TRIPLE_H + +#include +#include +#include + +#include "score/result/result.h" + +#include +#include +#include + +namespace flatcfg +{ +namespace config +{ +namespace _impl +{ + +/// @brief Base implementation of a daemon iterator where each underlying +/// iterator implicitly knows its lookup directories but must be advanced +/// manually. +class DaemonIteratorTriple +{ +public: + /// @brief Create a daemon iterator triple. + /// @post All underlying iterators are in a sentinel state. + DaemonIteratorTriple() = default; + + /// @brief Move constructible. + /// @note The moved-from instance will be put in a sentinel state. + DaemonIteratorTriple(DaemonIteratorTriple&&) = default; + + /// @brief Move assignable. + /// @note The moved-from instance will be put in a sentinel state. + DaemonIteratorTriple& + operator=(DaemonIteratorTriple&&) = default; + + /// @brief True if in an error state. + bool + isError() const noexcept; + + /// @brief True if not in an error state and all underlying iterators are + /// sentinels. + bool + isSentinel() const noexcept; + + /// @brief Is equivalent to !(isError() || isSentinel()). + // RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) + operator bool() const noexcept; + + /// @brief Get a view to the complete path of the current entry. + /// @pre operator bool() == true + std::string_view + path() const noexcept; + + /// @brief Get the current error state. + score::ResultBlank + getError() const noexcept; + + /// @brief Put the instance into an error state with the given error. + /// @pre !error.HasValue() + template + void + setError(const score::Result& error) noexcept + { + _setVoidError(score::MakeUnexpected(error.error())); + } + + /// @brief Advance the root iterator, constructed from /opt/score, to the + /// next directory entry. + /// @note If the root iterator is a sentinel when called, it will iterate + /// from the start of /opt/score, possibly removing the sentinel + /// state. + /// @pre !isError() && mount.isSentinel() + /// @post If true is returned, the root iterator will point to an entry + /// which represents a directory. + /// @returns The value of root.operator bool() after calling this function. + /// @note The path written to the buffer will match: + /// "/opt/score//" + bool + advanceRootEntry() noexcept; + + /// @brief Advance the mount iterator, constructed from "/opt/score/", + /// to the next directory entry. + /// @note If the mount iterator is a sentinel when called, it will iterate + /// from the start of the current root iterator entry, possibly + /// removing the sentinel state. + /// @note The next directory entry's name will have /etc appended to it, + /// and will be checked for validity. + /// @pre !isError() && root.operator bool() && swcl.isSentinel() + /// @post If true is returned, the mount iterator will point to an entry + /// which represents a directory. + /// @returns The value of mount.operator bool() after calling this function. + /// @note The path written to the buffer will match: + /// "/opt/score///etc/" + bool + advanceMountEntry() noexcept; + + /// @brief Advance the swcl iterator, constructed from + /// "/opt/score///etc", to the next entry. + /// @note If the swcl iterator is a sentinel when called, it will iterate + /// from the start of the current mount iterator entry, possibly + /// removing the sentinel state. + /// @pre !isError() && root.operator bool() && mount.operator bool() + /// @post If true is returned, the swcl iterator will point to an entry + /// which represents a regular file. + /// @post The path written to the buffer is not null-terminated, or + /// terminated with anything else. + /// @returns The value of swcl.operator bool() after calling this function. + /// @note The path written to the buffer will match: + /// "/opt/score///etc/" + bool + advanceSwclEntry() noexcept; + +private: + /// @brief Non-generic implementation of setError. + /// @pre !error.HasValue() + void + _setVoidError(score::ResultBlank error) noexcept; + + /// @brief Helper type combining a directory iterator and metadata. + // RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "All data members are constructed.", false) + struct DirWithInfo + { + /// @brief Directory iterator. + fs::DirectoryIterator it; + + /// @brief Size of the complete parent directory path in buffer + /// corresponding to iterator, including trailing separator. + std::size_t parentSize {}; + + /// @brief Size of file name in buffer corresponding to iterator position. + std::size_t nameSize {}; + }; + + /// @brief Array of our directory iterators and their metadata. + std::array m_dirs {}; + + /// @brief Statically sized buffer to hold the complete path to the current + /// directory entry. + std::array m_pathBuffer {}; + + /// @brief Error state owned independently of underlying iterators. + score::ResultBlank m_error {}; +}; + +} // namespace _impl +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_IMPL_DAEMON_ITERATOR_TRIPLE_H diff --git a/src/flatcfg/include/flatcfg/config/daemon_iterator.h b/src/flatcfg/include/flatcfg/config/daemon_iterator.h new file mode 100644 index 0000000..838320f --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/daemon_iterator.h @@ -0,0 +1,135 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_DAEMON_ITERATOR_H +#define FLATCFG_CONFIG_DAEMON_ITERATOR_H + +#include "_impl/daemon_iterator.h" + +#include + +#include + +namespace flatcfg +{ +namespace config +{ + +/// @brief Iterator to iterate through the config files relevant to a daemon. +/// @warning The size of this type exceeds three pages of memory. +class DaemonIterator +{ +public: + // iterator member typedefs + using difference_type = std::ptrdiff_t; + using value_type = score::Result; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::input_iterator_tag; + + /// @brief Create a daemon iterator which will iterate over relevant + /// configuration files in implicitly known daemon lookup directories. + static score::Result + FromInfo(const LookupInfo& lookupInfo) noexcept; + + /// @brief Create a daemon iterator which will iterate over relevant + /// configuration files in the directory referenced by the path. + static score::Result + FromPathAndInfo(std::string_view directoryPath, + const LookupInfo& lookupInfo) noexcept; + + /// @brief Default constructor produces a sentinel iterator, equivalent to end(). + DaemonIterator() noexcept; + + /// @brief Move constructible. + /// @note The moved-from iterator will become a sentinel iterator. + DaemonIterator(DaemonIterator&&) = default; + + /// @brief Move assignable. + /// @note The moved-from iterator will become a sentinel iterator. + DaemonIterator& + operator=(DaemonIterator&&) = default; + + /// @brief Progress to the next relevant configuration, or become a sentinel + /// iterator if there are no more relevant configurations. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + DaemonIterator& + operator++() noexcept; + + /// @brief Progress to the next relevant configuration, or become a sentinel + /// iterator if there are no more relevant configurations. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + void + operator++(int) noexcept; + + /// @brief Attempt to provide the current relevant configuration. + /// @note Any error encountered after construction is surfaced here. + /// @warning It is undefined behaviour to call this on a sentinel iterator. + value_type + operator*() noexcept; + + /// @brief True when called on an iterator in an error state. + bool + isError() const noexcept; + + /// @brief True when called on a sentinel iterator. + bool + isSentinel() const noexcept; + + /// @brief Is equivalent to !(isError() || isSentinel()). + // RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) + operator bool() const noexcept; + + /// @brief Equality comparison. + friend bool + operator==(const DaemonIterator& lhs, const DaemonIterator& rhs) noexcept; + + /// @brief Inequality comparison. + friend bool + operator!=(const DaemonIterator& lhs, const DaemonIterator& rhs) noexcept; + + /// @brief Equality comparison with sentinel. + friend bool + operator==(const DaemonIterator& lhs, Sentinel rhs) noexcept; + + /// @brief Inequality comparison with sentinel. + friend bool + operator!=(const DaemonIterator& lhs, Sentinel rhs) noexcept; + + /// @brief Reversed equality comparison with sentinel. + friend bool + operator==(Sentinel lhs, const DaemonIterator& rhs) noexcept; + + /// @brief Reversed inequality comparison with sentinel. + friend bool + operator!=(Sentinel lhs, const DaemonIterator& rhs) noexcept; + +private: + /// @brief Create a daemon iterator which will iterate over relevant + /// configuration files in implicitly known daemon lookup directories. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + DaemonIterator(const LookupInfo& lookupInfo, score::ResultBlank *resOut) noexcept; + + /// @brief Create a daemon iterator which will iterate over relevant + /// configuration files in the directory referenced by the path. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + DaemonIterator(std::string_view directoryPath, const LookupInfo& lookupInfo, + score::ResultBlank *resOut) noexcept; + + /// @brief Variable underlying iterator depending on if we're searching + /// known daemon directories or falling back to a single directory. + std::variant<_impl::DaemonIteratorImpl, ProcessIterator> m_itVar {}; +}; + +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_DAEMON_ITERATOR_H diff --git a/src/flatcfg/include/flatcfg/config/identifier.h b/src/flatcfg/include/flatcfg/config/identifier.h new file mode 100644 index 0000000..44e935f --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/identifier.h @@ -0,0 +1,131 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_HANDLE_H +#define FLATCFG_CONFIG_HANDLE_H + +#include "version.h" + +#include +#include + +#include "score/result/result.h" + +#include +#include +#include +#include + +namespace flatcfg +{ + + // forward declaration so that we can friend it in Identifier + class FlatCfg; + +namespace config +{ + +/// @brief Unique identifier for a configuration. +class Identifier +{ + // friend FlatCfg so that it can access the complete path + // RULECHECKER_comment(1, 1, check_friend_declaration, "Necessary for desired functionality.", true) + friend class flatcfg::FlatCfg; + +public: + /// @brief Construct an identifier from a path to a configuration file, and + /// the expected version it should meet. + /// @details The path is expected to match the following regex: + /// (.*/)? parent dirs + /// [a-z]+ function cluster + /// (_[a-z0-9](_?[a-z0-9]+)*)? process (leading underscore) + /// __.+ software cluster (leading double underscore) + /// _flatcfg\.bin suffix + static score::Result + FromPathAndVersion(std::string_view filePath, Version version) noexcept; + + /// @brief Copy (and move) constructible. + Identifier(const Identifier&) = default; + + /// @brief Copy (and move) assignable. + Identifier& + operator=(const Identifier&) = default; + + /// @brief Get the configuration's function cluster. + /// @note Will always be non-empty. + std::string_view + functionCluster() const noexcept; + + /// @brief Get the configuration's software cluster. + /// @note Will always be non-empty. + std::string_view + softwareCluster() const noexcept; + + /// @brief Get the configuration's optional process identifier. + /// @note Will always be non-empty if not std::nullopt. + std::optional + process() const noexcept; + + /// @brief Get the version the configuration is expected to meet. + Version + version() const noexcept; + + /// @brief Equality comparison. + friend bool + operator==(const Identifier& lhs, const Identifier& rhs) noexcept; + + /// @brief Inequality comparison. + friend bool + operator!=(const Identifier& lhs, const Identifier& rhs) noexcept; + +private: + /// @brief Construct an identifier from a path to a configuration file, and + /// the expected version it should meet. + /// @details The path is expected to match the following regex: + /// (.*/)? parent dirs + /// [a-z]+ function cluster + /// (_[a-z0-9](_?[a-z0-9]+)*)? process (leading underscore) + /// __.+ software cluster (leading double underscore) + /// _flatcfg\.bin suffix + /// @note If an error occurs, it is written to resOut, and the instance is considered invalid. + Identifier(std::string_view filePath, Version version, + score::ResultBlank *resOut) noexcept; + + /// @brief Get view of the underlying path. + std::string_view + path() const noexcept; + + /// @brief Statically sized buffer to hold the configuration file path. + std::array m_pathBuffer {}; + + /// @brief Size of complete path from start of path buffer, not including + /// null-terminator. + std::size_t m_pathSize {}; + + /// @brief Offset of function cluster from start of path buffer. + std::ptrdiff_t m_fcPos {}; + + /// @brief Size of function cluster from offset. + std::size_t m_fcSize {}; + + /// @brief Offset of software cluster from start of path buffer. + std::ptrdiff_t m_swclPos {}; + + /// @brief Size of software cluster from offset. + std::size_t m_swclSize {}; + + /// @brief Offset of optional process identifier from start of path buffer. + std::optional m_procPosOpt {}; + + /// @brief Size of optional process identifier from offset. + std::optional m_procSizeOpt {}; + + /// @brief Version the found file is expected to meet. + Version m_version {}; +}; + +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_HANDLE_H diff --git a/src/flatcfg/include/flatcfg/config/lookup_info.h b/src/flatcfg/include/flatcfg/config/lookup_info.h new file mode 100644 index 0000000..4778487 --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/lookup_info.h @@ -0,0 +1,89 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_LOOKUP_INFO_H +#define FLATCFG_CONFIG_LOOKUP_INFO_H + +#include "version.h" + +#include +#include + +#include "score/result/result.h" + +#include +#include +#include + +namespace flatcfg +{ +namespace config +{ + +/// @brief Class to hold information necessary to determine if a found configuration +/// is relevant or not. +class LookupInfo +{ +public: + /// @brief Create an instance of LookupInfo from values. + /// @details The values are expected to match the following regexes: + /// [a-z]+ function cluster + /// [a-z0-9](_?[a-z0-9]+)* process + /// The version can have any value. + static score::Result + FromValues(std::string_view functionCluster, + std::optional process, + Version version) noexcept; + + /// @brief Copy (and move) constructible. + LookupInfo(const LookupInfo&) = default; + + /// @brief Copy (and move) assignable. + LookupInfo& + operator=(const LookupInfo&) = default; + + /// @brief Get the relevant function cluster for a configuration. + std::string_view + functionCluster() const noexcept; + + /// @brief Get the optional relevant process for a configuration. + std::optional + process() const noexcept; + + /// @brief Get the expected version a configuration should meet. + Version + version() const noexcept; + + /// @brief Check if this info is relevant for a configuration. + bool + isRelevantFor(std::string_view functionCluster, + std::optional process) const noexcept; + +private: + /// @brief Create an instance of LookupInfo from values. + /// @details The values are expected to match the following regexes: + /// [a-z]+ function cluster + /// [a-z0-9](_?[a-z0-9]+)* process + /// The version can have any value. + LookupInfo(std::string_view functionCluster, + std::optional process, + Version version, score::ResultBlank *resOut) noexcept; + + /// @brief Statically sized buffer to hold lookup info values. + std::array m_buffer {}; + + /// @brief Size of function cluster from start of buffer. + std::size_t m_fcSize {}; + + /// @brief Size of optional process from end of function cluster + 1. + std::optional m_procSizeOpt {}; + + /// @brief Version the found file is expected to have. + Version m_version {}; +}; + +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_LOOKUP_INFO_H diff --git a/src/flatcfg/include/flatcfg/config/process_iterator.h b/src/flatcfg/include/flatcfg/config/process_iterator.h new file mode 100644 index 0000000..a7b9356 --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/process_iterator.h @@ -0,0 +1,190 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_PROCESS_ITERATOR_H +#define FLATCFG_CONFIG_PROCESS_ITERATOR_H + +#include "identifier.h" +#include "lookup_info.h" +#include "range.h" + +#include +#include +#include + +#include "score/result/result.h" + +#include +#include +#include +#include + +namespace flatcfg +{ +namespace config +{ + +/// @brief Iterator to iterate through the config files relevant to the current +/// process's executable. +/// @warning The size of this type exceeds a single page of memory. +class ProcessIterator +{ + // friend DaemonIterator so it can construct this type directly for better + // initialization of variant + // RULECHECKER_comment(1, 1, check_friend_declaration, "Necessary for desired functionality.", true) + friend class DaemonIterator; + +public: + // iterator member typedefs + using difference_type = std::ptrdiff_t; + using value_type = score::Result; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::input_iterator_tag; + + /// @brief Create a process iterator which will iterate over relevant + /// configuration files in the directory referenced by the path. + /// @note The directory path should not have a trailing separator, although + /// it is not an error if it does. + static score::Result + FromPathAndInfo(std::string_view directoryPath, + const LookupInfo& lookupInfo) noexcept; + + /// @brief Default constructor produces a sentinel iterator, equivalent to end(). + ProcessIterator() noexcept; + + /// @brief Move constructible. + /// @note The moved-from iterator will become a sentinel iterator. + // RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "All data members are constructed.", false) + ProcessIterator(ProcessIterator&&) = default; + + /// @brief Move assignable. + /// @note The moved-from iterator will become a sentinel iterator. + ProcessIterator& + operator=(ProcessIterator&&) = default; + + /// @brief Progress to the next relevant configuration, or become a sentinel + /// iterator if there are no more relevant configurations. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + ProcessIterator& + operator++() noexcept; + + /// @brief Progress to the next relevant configuration, or become a sentinel + /// iterator if there are no more relevant configurations. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + void + operator++(int) noexcept; + + /// @brief Attempt to provide the current relevant configuration. + /// @note Any error encountered after construction is surfaced here. + /// @warning It is undefined behaviour to call this on a sentinel iterator. + value_type + operator*() noexcept; + + /// @brief True when called on an iterator in an error state. + bool + isError() const noexcept; + + /// @brief True when called on a sentinel iterator. + bool + isSentinel() const noexcept; + + /// @brief Is equivalent to !(isError() || isSentinel()). + // RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) + operator bool() const noexcept; + + /// @brief Equality comparison. + friend bool + operator==(const ProcessIterator& lhs, const ProcessIterator& rhs) noexcept; + + /// @brief Inequality comparison. + friend bool + operator!=(const ProcessIterator& lhs, const ProcessIterator& rhs) noexcept; + + /// @brief Equality comparison with sentinel. + friend bool + operator==(const ProcessIterator& lhs, Sentinel rhs) noexcept; + + /// @brief Inequality comparison with sentinel. + friend bool + operator!=(const ProcessIterator& lhs, Sentinel rhs) noexcept; + + /// @brief Reversed equality comparison with sentinel. + friend bool + operator==(Sentinel lhs, const ProcessIterator& rhs) noexcept; + + /// @brief Reversed inequality comparison with sentinel. + friend bool + operator!=(Sentinel lhs, const ProcessIterator& rhs) noexcept; + +private: + /// @brief Create a process iterator which will iterate over relevant + /// configuration files in the directory referenced by the path. + /// @note The directory path should not have a trailing separator, although + /// it is not an error if it does. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + ProcessIterator(std::string_view directoryPath, const LookupInfo& lookupInfo, + score::ResultBlank *resOut) noexcept; + + /// @brief Get the current error state, either from the underlying iterator + /// or from the extra error state. + score::ResultBlank + getError() noexcept; + + /// @brief If the underlying iterator is in an error state, copy its error + /// and reset it to a sentinel. + /// @details This should be called at the exit of every non-const public + /// member function, to ensure that we do not keep directory + /// handles open longer than necessary. + void + copyAndClearIteratorError() noexcept; + + /// @brief Advance the iterator to the next entry. + /// @note If isError() or isSentinel() is true, this function does nothing. + /// @returns True if both isError() and isSentinel() would be false after + /// calling this function, otherwise false. + bool + advanceEntry() noexcept; + + /// @brief Save the name of the current or next relevant entry. + /// @details Advance the iterator to the next entry while both isError() and + /// isSentinel() are false, and the current entry does not refer to + /// a relevant configuration file. + /// If isError() and isSentinel() are still false, save the name of + /// the relevant entry in the path buffer. + void + skipIrrelevantEntriesAndSaveName() noexcept; + + /// @brief Directory iterator. + fs::DirectoryIterator m_dirIt {}; + + /// @brief Statically sized buffer to hold the complete path to the current + /// directory entry. + std::array m_pathBuffer {}; + + /// @brief Size of parent directory path in buffer, including trailing slash. + std::size_t m_parentDirSize {}; + + /// @brief Size of file name in buffer, not including leading slash. + std::size_t m_nameSize {}; + + /// @brief Extra information to check if found files are relevant. + LookupInfo m_info; + + /// @brief Error state owned independently of underlying iterator. + /// @note Prefer getError() over accessing this directly when possible. + score::ResultBlank m_error {}; +}; + +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_PROCESS_ITERATOR_H diff --git a/src/flatcfg/include/flatcfg/config/range.h b/src/flatcfg/include/flatcfg/config/range.h new file mode 100644 index 0000000..627c323 --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/range.h @@ -0,0 +1,24 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_RANGE_H +#define FLATCFG_CONFIG_RANGE_H + +namespace flatcfg +{ +namespace config +{ + +/// @brief Type which acts as a sentinel for all config iterators. +struct Sentinel {}; + +/// @brief Helper instance of sentinel type. +// RULECHECKER_comment(2, 1, check_single_use_pod_variable, "Constexpr instance provided for utility.", true) +// coverity[autosar_cpp14_a0_1_1_violation:FALSE] +static constexpr Sentinel sentinel {}; + +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_RANGE_H diff --git a/src/flatcfg/include/flatcfg/config/version.h b/src/flatcfg/include/flatcfg/config/version.h new file mode 100644 index 0000000..410c276 --- /dev/null +++ b/src/flatcfg/include/flatcfg/config/version.h @@ -0,0 +1,39 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_CONFIG_VERSION_H +#define FLATCFG_CONFIG_VERSION_H + +#include + +#include + +namespace flatcfg +{ +namespace config +{ + +/// @brief Helper struct to combine version components so that they don't get +/// mixed up when passed between functions. +struct Version +{ + /// @brief Major version component. + std::int32_t major {}; + + /// @brief Minor version component. + std::int32_t minor {}; + + /// @brief Equality comparison. + friend bool + operator==(Version lhs, Version rhs) noexcept; + + /// @brief Inequality comparison. + friend bool + operator!=(Version lhs, Version rhs) noexcept; +}; + +} // namespace config +} // namespace flatcfg + +#endif // FLATCFG_CONFIG_VERSION_H diff --git a/src/flatcfg/include/flatcfg/flatcfg.h b/src/flatcfg/include/flatcfg/flatcfg.h new file mode 100644 index 0000000..c2d33d5 --- /dev/null +++ b/src/flatcfg/include/flatcfg/flatcfg.h @@ -0,0 +1,227 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FLATCFG_H +#define FLATCFG_FLATCFG_H + +#include "config/daemon_iterator.h" +#include "config/identifier.h" +#include "config/process_iterator.h" +#include "config/version.h" +#include "flatcfg_error.h" +#include "_export.h" + +#include "flatbuffers/score.h" + +#include "score/result/result.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace flatcfg +{ + +/// @brief Class providing access to the configuration data of an application or +/// of a daemon. +/// @details FlatCfg provides access to the raw data in configurations relevant +/// to the application or daemon. Parsing this data is up to the user. +class FlatCfg +{ +public: + /// @brief Deleter used to destroy memory allocated by load. + struct Deleter + { + public: + /// @brief Construct deleter from any pmr allocator. + Deleter(std::pmr::polymorphic_allocator alloc, + std::size_t size) noexcept; + + /// @brief Destroy object and deallocate memory using stored allocator. + void + operator()(unsigned char *ptr) noexcept; + + private: + /// @brief Store allocator with which to deallocate. + std::pmr::polymorphic_allocator m_alloc; + + /// @brief Size to pass to deallocate. + std::size_t m_size; + }; + + /// @brief Create a FlatCfg instance from function cluster information + /// generated by the flatbuffers compiler. + template + static score::Result + FromFcInfo(const flatbuffers::score::FcBase& fcInfo) noexcept; + + /// @brief Copy (and move) constructible. + /// @note No dedicated move constructor because we don't want to invalidate + /// the existing instance. + // RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "All data members are constructed.", false) + FlatCfg(const FlatCfg&) = default; + + /// @brief Copy (and move) assignable. + /// @note No dedicated move assignment operator because we don't want to + /// invalidate the existing instance. + FlatCfg& + operator=(const FlatCfg&) = default; + + /// @brief Obtain an iterator which will yield identifiers to all + /// configurations relevant for a daemon. + score::Result + daemonConfig() const noexcept; + + /// @brief Obtain an iterator which will yield identifiers to all + /// configurations relevant for a process of the application. + score::Result + processConfig() const noexcept; + + /// @brief Get the number of bytes that would be requested from the + /// allocator if the same identifier were passed to load(...). + static score::Result + size(const config::Identifier& id) noexcept; + + /// @brief Load the raw data from the configuration referenced by the given + //// identifier into memory, using the user-provided allocator for any + /// dynamic memory allocations. + static score::Result> + load(const config::Identifier& id, + std::pmr::polymorphic_allocator alloc) noexcept; + +private: + /// @brief Create a FlatCfg instance from function cluster name and version + /// information. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + /// @pre Name is not empty, it must match "[a-zA-Z]+", and it must not + /// exceed 16 characters. + FlatCfg(std::string_view name, config::Version version, + score::ResultBlank *resOut) noexcept; + +public: + /// @brief Constructor + /// @details Constructor for the FlatCfg class + /// @param[in] fcInfo FC Information from FlatBuffers generated header + template + // RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "All data members are constructed.", false) + FlatCfg(const flatbuffers::score::FcBase& fcInfo) noexcept + : m_functionClusterName(fcInfo.name()), + m_functionClusterNameLowerCase(fcInfo.name()), + m_functionClusterMajorVersion(fcInfo.versionMajor()), + m_functionClusterMinorVersion(fcInfo.versionMinor()) + { + // fcName in the file path is expected to be lower-case + // currently flatbuffers requires (and provides) upper-case fcName + // we need both + using std::begin; + using std::end; + std::transform(begin(m_functionClusterName), end(m_functionClusterName), + begin(m_functionClusterNameLowerCase), [](unsigned char c) { + return std::tolower(static_cast(c)); }); + } + + /// @brief Returns the available SWClusters for the requested FC + /// @details getSwClusterList searches the configuration path for the application + /// and returns all SWClusters for the requested fc. + /// @return Names of the SWClusters that are available for the fc, + /// error if more than one process is configured in the configuration path. + /// @error FlatCfgErrorCode::kConfigFiles Returned if globbing for the config file in the given path fails. + /// @error FlatCfgErrorCode::kInvalidConfigPath Returned if the ENV-variable that points to the path is not defined or the given path does not exist. + virtual score::Result> + getSwClusterList() noexcept; + + /// @brief Load + /// @param[in] SwClusterName to load + /// @details Allocate memory and load the config file for the requested SWCluster + /// Memory is allocated and client have to take care about releasing when not needed + /// @return Shared pointer to FlatBuffer, error when file can't be loaded + /// @error FlatCfgErrorCode::kFileAccessError Returned if the file cannot be accessed. + /// @error FlatCfgErrorCode::kFileEmpty Returned if the file is empty. + /// @error FlatCfgErrorCode::kFileNotFound Returned if the file cannot be found. + /// @error FlatCfgErrorCode::kInvalidSWCLName Returned if the given SwClusterName cannot be found. + /// @error FlatCfgErrorCode::kMemoryAllocationFailed Returned if there is not enough memory to load the configurations. + virtual score::Result> + load(const std::string& SwClusterName) noexcept; + + /// @brief Virtual destructor. + virtual ~FlatCfg() noexcept = default; + +private: + /// Function cluster information + std::string m_functionClusterName; + std::string m_functionClusterNameLowerCase; + std::int32_t m_functionClusterMajorVersion {}; + std::int32_t m_functionClusterMinorVersion {}; + + /// Shared map which holds metadata for all SW Clusters + std::map m_SwClusterPathMap; + + /// Name of the environment variable that holds the path to the configuration files + // Type is a char* as it is only used with getenv which expects char* + static const std::string_view envVar_configPath; + + /// Wildcard pattern to match the configuration filenames (FC will be preponed later) + static const std::string_view configFileGlobPattern; + + /// @brief Get the path to the configuration files + /// @details This function determines the path to the configuration files and checks if it exists. + /// The path is read from the ENV-variable defined in FlatCfg::envVar_configPath. + /// @return Path to the configuration files, + /// error if it cannot be determined or does not exist. + /// @error FlatCfgErrorCode::kInvalidConfigPath Returned if the ENV-variable that points to the path is not defined or the given path does not exist. + score::Result getConfigPath() const noexcept; + + /// @brief Get a list of all config filenames for the requested fc that are stored in the configPath + /// @details This functions reads all config filenames for the requested fc that are stored in the given path. + /// @note As the used glob only has limited filtering power, there still may be false positives which have + /// to be filtered out (files with incorrect names that are nonetheless matched by the wildcard pattern) + /// @param[in] configPath Path in which the config files are searched. + /// @return Names of the configuration files that are available for the fc, + /// error if searching fails. + /// @error FlatCfgErrorCode::kConfigFiles Returned if globbing for the config file in the given path fails. + score::Result> + getConfigFiles(const std::string& configPath) const noexcept; + + /// @brief Checks if all configurations in the list belong to one process and returns the SWCluster names + /// @details This function checks the given list of filenames as follows: + /// - Filenames are conform to the rules + /// - Only one process is configured with the given files + /// - SWCluster is not empty + /// If the checks are successful a list of all SWClusters that are configured in the list is returned. + /// @param[in] fileList List of files to be checked. + /// @return List of SWClusters for which a configuration was found, + /// error if a check failed + /// @error FlatCfgErrorCode::kInvalidFileName Returned if the config files name is invalid. + /// @error FlatCfgErrorCode::kMultipleProcessesInPath Returned If the configuration folder contains configurations for more than one process. + score::Result> + checkFilesAndGetSwClusterList(const std::vector& fileList) noexcept; +}; + +template +score::Result +FlatCfg::FromFcInfo(const flatbuffers::score::FcBase& fcInfo) noexcept +{ + score::ResultBlank res; + config::Version version { fcInfo.versionMajor(), fcInfo.versionMinor() }; + FlatCfg cfg { fcInfo.name(), version, &res }; + if (res) + { + return cfg; + } + else + { + return score::MakeUnexpected(res.error()); + } +} + +} // namespace flatcfg + +#endif // FLATCFG_FLATCFG_H diff --git a/src/flatcfg/include/flatcfg/flatcfg_error.h b/src/flatcfg/include/flatcfg/flatcfg_error.h new file mode 100644 index 0000000..b2ff7ca --- /dev/null +++ b/src/flatcfg/include/flatcfg/flatcfg_error.h @@ -0,0 +1,193 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FLATCFG_ERROR_H +#define FLATCFG_FLATCFG_ERROR_H + +#include "_export.h" + +#include "score/result/error.h" +#include "score/result/error_domain.h" + +namespace flatcfg +{ + +/// @brief Error codes for FlatCfg. +enum class FlatCfgErrorCode : score::result::ErrorCode +{ + // old error codes + kNotInitialized = 256, + kGeneric = 257, + kParameter = 258, + kInvalidConfigPath = 259, + kInvalidFileName = 260, + kConfigFiles = 261, + kMultipleProcessesInPath = 262, + kMemoryAllocationFailed = 263, + kMemoryAlreadyAllocated = 264, + kInvalidSWCLName = 265, + kFileNotFound = 266, + kFileEmpty = 267, + kFileAccessError = 268, + kInvalidBinaryFormat = 269, + kBadName = 270, + kBadVersionMajor = 271, + kBadVersionMinor = 272, + kInvalidDirectory = 273, + kUnknown = 511, + + /// new error codes + kBadLength, + kBadPathName, + kBadArgument, + kHandleClosed, + kBadAlloc, + kOsError, + kBadFormat = kInvalidBinaryFormat, + kVersionMismatch = kBadVersionMajor, +}; + +class FlatCfgErrorDomain final : public score::result::ErrorDomain +{ +public: + /// @brief Returns the message corresponding to a FlatCfg error code . + inline std::string_view MessageFor(const score::result::ErrorCode& errorCode) const noexcept override; +}; + +namespace global +{ + +static constexpr FlatCfgErrorDomain g_flatcfgErrorDomain {}; + +} // namespace global + +/// @brief Get a reference to a FlatCfg error domain object with static lifetime. +inline constexpr const score::result::ErrorDomain& +GetFlatCfgDomain() noexcept +{ + return global::g_flatcfgErrorDomain; +} + +inline score::result::Error MakeError(const FlatCfgErrorCode code, const std::string_view message = "") +{ + return {static_cast(code), GetFlatCfgDomain(), message}; +} + +std::string_view +FlatCfgErrorDomain::MessageFor(const score::result::ErrorCode& errorCode) const noexcept +{ + const auto code { static_cast(errorCode) }; + switch (code) + { + case FlatCfgErrorCode::kNotInitialized: + { + return "FlatCfg was not initialized before another function was called"; + } + case FlatCfgErrorCode::kGeneric: + { + return "Generic internal error"; + } + case FlatCfgErrorCode::kParameter: + { + return "Parameter error"; + } + case FlatCfgErrorCode::kInvalidConfigPath: + { + return "Root path to the configuration files is invalid"; + } + case FlatCfgErrorCode::kInvalidFileName: + { + return "Found filename that is not conform to the rules"; + } + case FlatCfgErrorCode::kConfigFiles: + { + return "Handling the config files resulted in an error"; + } + case FlatCfgErrorCode::kMultipleProcessesInPath: + { + return "Configuration folder contains configurations for more than one process"; + } + case FlatCfgErrorCode::kMemoryAllocationFailed: + { + return "Failed to allocate memory"; + } + case FlatCfgErrorCode::kMemoryAlreadyAllocated: + { + return "Memory is already allocated"; + } + case FlatCfgErrorCode::kInvalidSWCLName: + { + return "Invalid SWCluster name"; + } + case FlatCfgErrorCode::kFileNotFound: + { + return "File not found"; + } + case FlatCfgErrorCode::kFileEmpty: + { + return "File is empty"; + } + case FlatCfgErrorCode::kFileAccessError: + { + return "File access error"; + } + case FlatCfgErrorCode::kInvalidBinaryFormat: + { + return "Invalid binary format"; + } + case FlatCfgErrorCode::kBadName: + { + return "Unexpected function cluster name"; + } + case FlatCfgErrorCode::kBadVersionMajor: + { + return "Unexpected major version"; + } + case FlatCfgErrorCode::kBadVersionMinor: + { + return "Unexpected minor version"; + } + case FlatCfgErrorCode::kInvalidDirectory: + { + return "Invalid directory"; + } + case FlatCfgErrorCode::kBadLength: + { + return "The size of a buffer was too small to complete the operation"; + } + case FlatCfgErrorCode::kBadPathName: + { + return "The path (or a component of the path) is empty, contains a" + " null byte, or is not null-terminated when it is required" + " to be"; + } + case FlatCfgErrorCode::kBadArgument: + { + return "The value of a function argument did not meet the expected" + " pre-conditions"; + } + case FlatCfgErrorCode::kHandleClosed: + { + return "Attempted to call a function on a closed file or directory" + " handle"; + } + case FlatCfgErrorCode::kBadAlloc: + { + return "Failed to allocate memory"; + } + case FlatCfgErrorCode::kOsError: + { + return "Calling an OS function resulted in an error that was not EINTR"; + } + case FlatCfgErrorCode::kUnknown: + default: + { + return "An unknown error occurred"; + } + } +} + +} // namespace flatcfg + +#endif // FLATCFG_FLATCFG_ERROR_H diff --git a/src/flatcfg/include/flatcfg/fs/_os.h b/src/flatcfg/include/flatcfg/fs/_os.h new file mode 100644 index 0000000..370cda8 --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/_os.h @@ -0,0 +1,126 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_INTERNAL_OS_H +#define FLATCFG_FS_INTERNAL_OS_H + +/// @brief Compiling for a Linux platform. +#if defined(__linux__) + #define FLATCFG_INTERNAL_HAS_LINUX 1 +#else + #define FLATCFG_INTERNAL_HAS_LINUX 0 +#endif + +/// @brief Compiling for a QNX platform. +#if defined(__QNXNTO__) + #define FLATCFG_INTERNAL_HAS_QNX 1 +#else + #define FLATCFG_INTERNAL_HAS_QNX 0 +#endif + +/// @brief Compiling for a MacOS platform. +#if defined(__MACH__) + #define FLATCFG_INTERNAL_HAS_MACOS 1 +#else + #define FLATCFG_INTERNAL_HAS_MACOS 0 +#endif + +// ensure only a single platform is defined +#if (FLATCFG_INTERNAL_HAS_LINUX + \ + FLATCFG_INTERNAL_HAS_MACOS + \ + FLATCFG_INTERNAL_HAS_QNX) > 1 + #error Cannot support multiple platforms at the same time +#endif + +/// @brief Compiling for a platform with POSIX support. +#if FLATCFG_INTERNAL_HAS_LINUX || FLATCFG_INTERNAL_HAS_QNX || FLATCFG_INTERNAL_HAS_MACOS + #define FLATCFG_INTERNAL_HAS_POSIX 1 +#else + #define FLATCFG_INTERNAL_HAS_POSIX 0 +#endif + +#include +#include +#include + +#if FLATCFG_INTERNAL_HAS_POSIX + #include +#endif + +namespace flatcfg +{ +namespace fs +{ +namespace _os +{ + +/// @brief Maximum size of an entry name, not including null-terminator. +/// @note OS dependent. +// coverity[autosar_cpp14_a0_1_1_violation] #49860: This constant variable is used to define a maximum length, for this reason it is defined in the global context. +static constexpr unsigned int MAX_NAME_LENGTH = +#if FLATCFG_INTERNAL_HAS_POSIX + static_cast(NAME_MAX); +#else + #error Unsupported OS +#endif + +/// @brief Maximum size of a path, not including null-terminator. +/// @note OS dependent. +// coverity[autosar_cpp14_a0_1_1_violation] #49860: This constant variable is used to define a maximum length, for this reason it is defined in the global context. +static constexpr unsigned int MAX_PATH_LENGTH = +#if FLATCFG_INTERNAL_HAS_POSIX + static_cast(PATH_MAX); +#else + #error Unsupported OS +#endif + +/// @brief Native handle type to directory, usually corresponding to a file descriptor. +/// @note OS dependent. +using DirHandleT = +#if FLATCFG_INTERNAL_HAS_POSIX + int; +#else + #error Unsupported OS +#endif + +/// @brief Native handle type to a file, usually corresponding to a file descriptor. +/// @note OS dependent. +using FileHandleT = +#if FLATCFG_INTERNAL_HAS_POSIX + int; +#else + #error Unsupported OS +#endif + +/// @brief Native handle type to directory entry. +/// @note OS dependent. +/// @warning This type may be the type used by an internal kernel level API +/// rather than a public libc API. +using DirEntryKernelT = +#if FLATCFG_INTERNAL_HAS_POSIX + struct dirent; +#else + #error Unsupported OS +#endif + +/// @brief The minimum size of a buffer which can be passed to the underlying +/// OS API used by readdir. +/// @note OS dependent. +/// @warning This is usually the block size of the filesystem which the directory +/// is stored on, but can be different if checked with the implementation. +// coverity[autosar_cpp14_a0_1_1_violation:FALSE] +static constexpr unsigned int SAFE_READDIR_BUFFER_SIZE = +#if FLATCFG_INTERNAL_HAS_POSIX + // technically 8kb block sizes exist, but no one uses them + // qnx 7.1.0's readdir implementation has this value hardcoded + 4096U; +#else + #error Unsupported OS +#endif + +} // namespace _os +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_FS_INTERNAL_OS_H diff --git a/src/flatcfg/include/flatcfg/fs/directory.h b/src/flatcfg/include/flatcfg/fs/directory.h new file mode 100644 index 0000000..6a97142 --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/directory.h @@ -0,0 +1,13 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_DIRECTORY_H +#define FLATCFG_FS_DIRECTORY_H + +#include "directory/entry.h" +#include "directory/entry_view.h" +#include "directory/handle.h" +#include "directory/iterator.h" + +#endif // FLATCFG_FS_DIRECTORY_H diff --git a/src/flatcfg/include/flatcfg/fs/directory/entry.h b/src/flatcfg/include/flatcfg/fs/directory/entry.h new file mode 100644 index 0000000..af0e933 --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/directory/entry.h @@ -0,0 +1,66 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_DIRECTORY_ENTRY_H +#define FLATCFG_FS_DIRECTORY_ENTRY_H + +#include "entry_view.h" + +#include "flatcfg/fs/_os.h" +#include "flatcfg/_export.h" + +#include "score/result/result.h" + +#include +#include +#include + +namespace flatcfg +{ +namespace fs +{ + +/// @brief Holds all information for a directory entry. +class DirectoryEntry +{ +public: + /// @brief Create an entry from its view counterpart. + static score::Result + FromView(DirectoryEntryView view) noexcept; + + /// @brief Copy (and move) constructible. + DirectoryEntry(const DirectoryEntry&) = default; + + /// @brief Copy (and move) assignable. + DirectoryEntry& + operator=(const DirectoryEntry&) = default; + + /// @brief Name of the entry. + /// @note Will always be non-empty and null-terminated. + std::string_view + name() const noexcept; + + /// @brief Type of the entry. + FileType + type() const noexcept; + +private: + /// @brief Create an entry from its view counterpart. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + DirectoryEntry(DirectoryEntryView view, score::ResultBlank *resOut) noexcept; + + /// @brief Statically sized buffer to hold the entry name. + std::array m_nameBuffer {}; + + /// @brief Size of entry name, not including null-terminator. + std::size_t m_nameSize {}; + + /// @brief Type of the entry. + FileType m_type {}; +}; + +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_FS_DIRECTORY_ENTRY_H diff --git a/src/flatcfg/include/flatcfg/fs/directory/entry_view.h b/src/flatcfg/include/flatcfg/fs/directory/entry_view.h new file mode 100644 index 0000000..55284e1 --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/directory/entry_view.h @@ -0,0 +1,89 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_DIRECTORY_ENTRY_VIEW_H +#define FLATCFG_FS_DIRECTORY_ENTRY_VIEW_H + +#include "flatcfg/fs/file/type.h" +#include "flatcfg/fs/_os.h" +#include "flatcfg/_export.h" + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace flatcfg +{ +namespace fs +{ + +/// @brief Cheap directory entry type where the name field is a view rather than owned. +class DirectoryEntryView +{ +public: + /// @brief Typedef because name on some platforms uses wchar_t. +#if FLATCFG_INTERNAL_HAS_POSIX + using ViewT = std::string_view; +#else + #error Unsupported OS +#endif + + /// @brief Create an instance of DirectoryEntryView. + /// @pre The name is non-empty and does not contain null bytes. + static score::Result + FromValues(std::uint64_t id, std::uint64_t entrySize, FileType type, + ViewT name) noexcept; + + /// @brief Copy (and move) constructible. + DirectoryEntryView(const DirectoryEntryView&) = default; + + /// @brief Copy (and move) assignable. + DirectoryEntryView& + operator=(const DirectoryEntryView&) = default; + + /// @brief Get the file identifier (usually corresponding to inode). + std::uint64_t + id() const noexcept; + + /// @brief Get the size of the complete entry in bytes. + std::uint64_t + entrySize() const noexcept; + + /// @brief Get the type of the file. + FileType + type() const noexcept; + + /// @brief Get non-empty view of the file name which does not contain null + /// bytes in the platform native format. + ViewT + name() const noexcept; + +private: + /// @brief Create an instance of DirectoryEntryView where the name is a view + /// into the provided buffer. + /// @pre The name is non-empty and does not contain null bytes. + /// @note If an error occurs, it is written to resOut, and the instance is considered invalid. + DirectoryEntryView(std::uint64_t id, std::uint64_t entrySize, FileType type, + ViewT name, score::ResultBlank *resOut) noexcept; + + /// @brief File identifier (usually corresponding to inode). + std::uint64_t m_id {}; + + /// @brief Size of the complete entry in bytes. + std::uint64_t m_entrySize {}; + + /// @brief Type of the file. + FileType m_type {}; + + /// @brief Non-empty view of the file name which does not contain null bytes + /// in the platform native format. + ViewT m_name {}; +}; + +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_FS_DIRECTORY_ENTRY_VIEW_H diff --git a/src/flatcfg/include/flatcfg/fs/directory/handle.h b/src/flatcfg/include/flatcfg/fs/directory/handle.h new file mode 100644 index 0000000..cad2346 --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/directory/handle.h @@ -0,0 +1,92 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_DIRECTORY_HANDLE_H +#define FLATCFG_FS_DIRECTORY_HANDLE_H + +#include "entry_view.h" + +#include "flatcfg/fs/_os.h" +#include "flatcfg/_export.h" + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include + +namespace flatcfg +{ +namespace fs +{ + +/// @brief Thin wrapper around an OS specific directory handle type. +class DirectoryHandle +{ +public: + /// @brief Create a handle from the directory referenced by the path. + /// @pre The directory path is null-terminated. + static score::Result + FromPath(std::string_view directoryPath) noexcept; + + /// @brief Check if a path references a directory. + /// @pre The path is null-terminated. + static score::Result + isDirectoryPath(std::string_view path) noexcept; + + /// @brief Close the underlying native handle. + ~DirectoryHandle() noexcept; + + /// @brief Move constructible. + /// @note The moved-from handle is closed. Calling a member function on it + /// will return an error. + DirectoryHandle(DirectoryHandle&& other) noexcept; + + /// @brief Move assignable. + /// @note The moved-from handle is closed. Calling a member function on it + /// will return an error. + DirectoryHandle& + operator=(DirectoryHandle&& other) noexcept; + + /// @brief Read one or more directory entries in an OS specific format into + /// the buffer provided. + /// @warning This format might be specific to kernel API functions, and differ + /// from common userspace formats (e.g. userspace dirent type). + /// @pre The buffer size must be greater than or equal to the block size + /// associated with the directory. + /// @note This function will only write complete entries into the buffer. + /// @returns The number of bytes copied into the buffer. Zero bytes means + /// that there are no more entries, equivalent to EOF. + score::Result + readEntriesIntoBuffer(score::cpp::span outputBuffer) const noexcept; + + /// @brief Parse a single entry from a buffer in the format written using + /// readEntriesIntoBuffer. + /// @warning This format might be specific to kernel API functions, and differ + /// from common userspace formats (e.g. userspace dirent type). + /// @pre The buffer points to the start of a complete entry created by + /// readEntriesIntoBuffer, with no alignment requirement on the entry. + score::Result + parseEntryFromBuffer(score::cpp::span buffer) const noexcept; + +private: + /// @brief Creates a handle for the directory referenced by the path. + /// @note If an error occurs, it is written to resOut, and the instance is considered invalid. + /// @pre The directory path is null-terminated. + DirectoryHandle(std::string_view directoryPath, score::ResultBlank *resOut) noexcept; + + /// @brief The underlying native handle. + std::optional<_os::DirHandleT> m_handleOpt {}; + + /// @brief Owned copy of the directory path. + /// @note Necessary because QNX doesn't properly implement fstatat. + std::string m_directoryPath {}; +}; + +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_FS_DIRECTORY_HANDLE_H diff --git a/src/flatcfg/include/flatcfg/fs/directory/iterator.h b/src/flatcfg/include/flatcfg/fs/directory/iterator.h new file mode 100644 index 0000000..16260c1 --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/directory/iterator.h @@ -0,0 +1,148 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_DIRECTORY_ITERATOR_H +#define FLATCFG_FS_DIRECTORY_ITERATOR_H + +#include "entry.h" +#include "entry_view.h" +#include "handle.h" + +#include "flatcfg/_export.h" + +#include "score/result/result.h" + +#include +#include +#include +#include +#include + +namespace flatcfg +{ +namespace fs +{ + +/// @brief Iterator to iterate through a directory's entries. +/// @warning The size of this type exceeds a single page of memory. +class DirectoryIterator +{ +public: + // iterator member typedefs + using difference_type = std::ptrdiff_t; + using value_type = score::Result; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::input_iterator_tag; + + /// @brief Create a directory iterator pointing to the directory referenced + /// by the path. + /// @pre The directory path is null-terminated. + static score::Result + FromPath(std::string_view directoryPath) noexcept; + + /// @brief Default constructor produces a sentinel iterator, equivalent to end(). + DirectoryIterator() noexcept; + + /// @brief Move constructible. + /// @note The moved-from iterator will become a sentinel iterator. + DirectoryIterator(DirectoryIterator&& other) noexcept; + + /// @brief Move assignable. + /// @note The moved-from iterator will become a sentinel iterator. + DirectoryIterator& + operator=(DirectoryIterator&& other) noexcept; + + /// @brief Progress to the next directory entry, or become a sentinel iterator + /// if there are no more entries. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + DirectoryIterator& + operator++() noexcept; + + /// @brief Progress to the next directory entry, or become a sentinel iterator + /// if there are no more entries. + /// @note This is valid to call if the iterator is a sentinel, in which + /// case it will be unmodified. + /// @warning It is undefined behaviour to call this if the iterator is in an + /// error state. + void + operator++(int) noexcept; + + /// @brief Attempt to provide the current directory entry. + /// @note Any error encountered after construction is surface here. + /// @warning It is undefined behaviour to call this on a sentinel iterator. + value_type + operator*() noexcept; + + /// @brief True when called on an iterator in an error state. + bool + isError() const noexcept; + + /// @brief True when called on a sentinel iterator. + bool + isSentinel() const noexcept; + + /// @brief Is equivalent to !(isError() || isSentinel()). + // RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) + operator bool() const noexcept; + + /// @brief Equality comparison. + // RULECHECKER_comment(1, 1, check_friend_declaration, "Friend is expected for non-member operators.", true) + friend bool + operator==(const DirectoryIterator& lhs, const DirectoryIterator& rhs) noexcept; + + /// @brief Inequality comparison. + friend bool + operator!=(const DirectoryIterator& lhs, const DirectoryIterator& rhs) noexcept; + +private: + /// @brief Create a directory iterator pointing to the directory referenced by the path. + /// @note If an error occurs, it is written to resOut and the instance is considered invalid. + /// @pre The directory path is null-terminated. + DirectoryIterator(std::string_view directoryPath, score::ResultBlank *resOut) noexcept; + + /// @brief Get a view to the entry in the buffer at the current offset. + /// @note This will not set m_handleOptRes to an error on failure. + /// @pre operator bool() == true + score::Result + getCurrentEntryView() const noexcept; + + /// @brief Set the buffer position to the next valid directory entry if the + /// current directory entry is invalid/deleted (i.e. if d_ino == 0). + /// If the current directory entry is valid, the position is not + /// advanced. + /// @note This will not set m_handleOptRes to an error on failure. + /// @details If the last directory entry is invalid/deleted, the buffer position + /// is set to the size of the buffer. + /// @pre operator bool() == true + score::ResultBlank + skipInvalidEntries() noexcept; + + /// @brief Statically sized buffer to hold entries read from the directory + /// handle. + std::array m_buffer {}; + + /// @brief Offset of data in buffer from the start of the buffer. + std::size_t m_bufferPos {}; + + /// @brief Size of data in buffer from the start of the buffer. + std::size_t m_bufferSize {}; + + /// @brief Directory handle. + /// @details We have no mechanism to recover from errors, so any error is + /// permanent (unless replaced by assignment), and will be propagated + /// by any function trying to use the handle. + /// If nullopt is contained in the result type, then we are a + /// sentinel iterator. + score::Result> m_handleOptRes {}; +}; + +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_FS_DIRECTORY_ITERATOR_H diff --git a/src/flatcfg/include/flatcfg/fs/file.h b/src/flatcfg/include/flatcfg/fs/file.h new file mode 100644 index 0000000..95a6b5f --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/file.h @@ -0,0 +1,10 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_FILE_H +#define FLATCFG_FS_FILE_H + +#include "file/type.h" + +#endif // FLATCFG_FS_FILE_H diff --git a/src/flatcfg/include/flatcfg/fs/file/type.h b/src/flatcfg/include/flatcfg/fs/file/type.h new file mode 100644 index 0000000..5628fba --- /dev/null +++ b/src/flatcfg/include/flatcfg/fs/file/type.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_FS_FILE_TYPE_H +#define FLATCFG_FS_FILE_TYPE_H + +namespace flatcfg +{ +namespace fs +{ + +/// @brief Represents the type of a file. +enum class FileType +{ + /// @brief The file type could not be determined. + UNKNOWN = 0 + + /// @brief This is a regular file. + ,REGULAR + + /// @brief This is a directory. + ,DIRECTORY + + /// @brief This is a character device. + ,CHARACTER + + /// @brief This is a block device. + ,BLOCK + + /// @brief This is a named pipe (FIFO). + ,FIFO + + /// @brief This is a symbolic link. + ,SYMLINK + + /// @brief This is a socket. + ,SOCKET +}; + +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_FS_FILE_TYPE_H diff --git a/src/flatcfg/src/flatcfg/flatcfg.cpp b/src/flatcfg/src/flatcfg/flatcfg.cpp new file mode 100644 index 0000000..d68d915 --- /dev/null +++ b/src/flatcfg/src/flatcfg/flatcfg.cpp @@ -0,0 +1,423 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "flatcfg/flatcfg.h" +#include "flatcfg/fs/_os.h" +#include "flatcfg/utils/ctc.h" +#include "flatcfg/utils/environment.h" +#include "flatcfg/utils/fb_verifier.h" +#include "flatcfg/utils/libc.h" +#include "flatcfg/utils/logging.h" +#include "flatcfg/utils/program_info.h" +#include "flatcfg/flatcfg_error.h" + +#include +#include +#include + +using score::cpp::span; + +namespace +{ + +// use constants in case of unexpected character set (i.e. non-ascii) +// cannot use setlocale + tolower/islower because not thread safe +// coverity[autosar_cpp14_m3_4_1_violation:Intentional] Defined here for better maintainability. +constexpr std::string_view ALPHA_UPPER { "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26U }; +// coverity[autosar_cpp14_m3_4_1_violation:Intentional] Defined here for better maintainability. +constexpr std::string_view ALPHA_LOWER { "abcdefghijklmnopqrstuvwxyz", 26U }; +// coverity[autosar_cpp14_m3_4_1_violation:Intentional] Defined here for better maintainability. +constexpr std::string_view ALPHA_CHARS { + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + 52U +}; + +} // namespace + +FLATCFG_SKIPCOV_BEGIN(); + +namespace flatcfg +{ + +FlatCfg::Deleter::Deleter(std::pmr::polymorphic_allocator alloc, + std::size_t size) noexcept + : m_alloc(std::move(alloc)), + m_size(size) +{} + +void +FlatCfg::Deleter::operator()(unsigned char *ptr) noexcept +{ + // rebind allocator + using AllocVoid = std::pmr::polymorphic_allocator; + using AllocVoidTraits = std::allocator_traits; + using Alloc = AllocVoidTraits::rebind_alloc; + using AllocTraits = AllocVoidTraits::rebind_traits; + Alloc reboundAlloc { m_alloc }; + + // unsigned char[] has implicit lifetime + // no need to destroy anything, only deallocate + AllocTraits::deallocate(reboundAlloc, ptr, m_size); +} + +// RULECHECKER_comment(1, 3, check_incomplete_data_member_construction, "All data members are constructed.", false) +// coverity[autosar_cpp14_a0_1_3_violation:Intentional] Function is called by template function in header, so appears unused but is actually used. +// coverity[autosar_cpp14_a8_4_10_violation:Intentional] Pointer to mark an out parameter. +FlatCfg::FlatCfg(std::string_view name, config::Version version, score::ResultBlank *resOut) noexcept + : m_functionClusterMajorVersion(version.major), + m_functionClusterMinorVersion(version.minor) +{ + // check that name isn't too large or empty + if (name.empty()) + { + FLATCFG_LOG_ERROR() << + "functionCluster name cannot be empty"; + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + if (name.size() > 16U) + { + FLATCFG_LOG_ERROR() << + "functionCluster name size (" << name.size() << ") cannot exceed" + " 16 bytes"; + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + return; + } + + // check that name matches [a-zA-Z]+ + bool ok = std::all_of(name.begin(), name.end(), [](char c) noexcept -> bool { + return ALPHA_CHARS.find(c) != std::string_view::npos; + }); + if (!ok) + { + FLATCFG_LOG_ERROR() << + "functionCluster name does not match '[a-zA-Z]+': " << name; + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + return; + } + + // save as original and lower case + FLATCFG_LOG_INFO() << "Initializing FlatCfg instance with version { .major=" + << version.major << ", .minor=" << version.minor << " } and function " + "cluster: " << name; + m_functionClusterName = name; + m_functionClusterNameLowerCase.resize(name.size()); + std::transform(m_functionClusterName.begin(), m_functionClusterName.end(), + m_functionClusterNameLowerCase.begin(), [](char c) noexcept -> char { + auto upperPos = ALPHA_UPPER.find(c); + return (upperPos != std::string_view::npos) ? ALPHA_LOWER[upperPos] : c; + }); +} + +score::Result +FlatCfg::daemonConfig() const noexcept +{ + // try to read PROCESSIDENTIFIER environment variable + std::array procBuffer {}; + span procSpan { procBuffer.data(), procBuffer.size() }; + auto procSsizeOpt = utils::Environment::processIdentifier(procSpan); + + // parse result + std::optional procOpt; + if (procSsizeOpt.has_value()) + { + std::ptrdiff_t ssize = *procSsizeOpt; + if (ssize < 0) + { + // value was too large to store in buffer + FLATCFG_LOG_ERROR() << + "environment variable PROCESSIDENTIFIER could not be read" + " because it exceeds the size of the buffer: " << + procBuffer.size(); + // coverity[stack_use_local_overflow:Intentional] Store large object on stack to avoid dynamic allocation. + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + else + { + std::size_t size = static_cast(ssize); + procOpt = std::string_view { procBuffer.data(), size }; + } + } + + // try to read ECUCFG_ENV_VAR_ROOTFOLDER environment variable + std::array rootBuffer {}; + span rootSpan { rootBuffer.data(), rootBuffer.size() }; + auto rootSsizeOpt = utils::Environment::ecuCfgRootFolder(rootSpan); + + // parse result + std::optional rootOpt; + if (rootSsizeOpt.has_value()) + { + std::ptrdiff_t ssize = *rootSsizeOpt; + if (ssize < 0) + { + // value was too large to store in buffer + FLATCFG_LOG_ERROR() << + "environment variable ECUCFG_ENV_VAR_ROOTFOLDER could not be" + " read because it exceeds the size of the buffer: " << + procBuffer.size(); + // coverity[stack_use_local_overflow:Intentional] Store large object on stack to avoid dynamic allocation. + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + else + { + std::size_t size = static_cast(ssize); + rootOpt = std::string_view { rootBuffer.data(), size }; + } + } + + // make lookup info + std::string_view fc { m_functionClusterNameLowerCase }; + config::Version version { m_functionClusterMajorVersion, m_functionClusterMinorVersion }; + auto infoRes = config::LookupInfo::FromValues(fc, procOpt, version); + if (!infoRes) + { + infoRes = config::LookupInfo::FromValues(fc, std::nullopt, version); + if (!infoRes) + { + // coverity[stack_use_local_overflow:Intentional] Store large object on stack to avoid dynamic allocation. + return score::MakeUnexpected(infoRes.error()); + } + } + const config::LookupInfo& info = *infoRes; + + // create iterator + // coverity[autosar_cpp14_a18_5_8_violation:Intentional] Large object on heap to avoid dynamic memory allocation. + // coverity[stack_use_local_overflow:Intentional] Store large object on stack to avoid dynamic allocation. + score::Result itRes; + if (rootOpt.has_value()) + { + // coverity[stack_use_local_overflow:Intentional] Store large object on stack to avoid dynamic allocation. + return config::DaemonIterator::FromPathAndInfo(*rootOpt, info); + } + else + { + // coverity[stack_use_local_overflow:Intentional] Store large object on stack to avoid dynamic allocation. + return config::DaemonIterator::FromInfo(info); + } +} + +score::Result +FlatCfg::processConfig() const noexcept +{ + // scope to limit size of stack frame + std::optional infoOpt; + { + // try to read PROCESSIDENTIFIER environment variable + std::array procBuffer {}; + span procSpan { procBuffer.data(), procBuffer.size() }; + auto procSsizeOpt = utils::Environment::processIdentifier(procSpan); + + // parse result + std::optional procOpt; + if (procSsizeOpt.has_value()) + { + std::ptrdiff_t ssize = *procSsizeOpt; + if (ssize < 0) + { + // value was too large to store in buffer + FLATCFG_LOG_ERROR() << + "environment variable PROCESSIDENTIFIER could not be read" + " because it exceeds the size of the buffer: " << + procBuffer.size(); + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + else + { + std::size_t size = static_cast(ssize); + procOpt = std::string_view { procBuffer.data(), size }; + } + } + + // try to read ECUCFG_ENV_VAR_ROOTFOLDER environment variable + std::array rootBuffer {}; + span rootSpan { rootBuffer.data(), rootBuffer.size() }; + auto rootSsizeOpt = utils::Environment::ecuCfgRootFolder(rootSpan); + + // parse result + std::optional rootOpt; + if (rootSsizeOpt.has_value()) + { + std::ptrdiff_t ssize = *rootSsizeOpt; + if (ssize < 0) + { + // value was too large to store in buffer + FLATCFG_LOG_ERROR() << + "environment variable ECUCFG_ENV_VAR_ROOTFOLDER could not be" + " read because it exceeds the size of the buffer: " << + procBuffer.size(); + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + else + { + std::size_t size = static_cast(ssize); + rootOpt = std::string_view { rootBuffer.data(), size }; + } + } + + // make lookup info + std::string_view fc { m_functionClusterNameLowerCase }; + config::Version version { m_functionClusterMajorVersion, m_functionClusterMinorVersion }; + auto infoRes = config::LookupInfo::FromValues(fc, procOpt, version); + if (!infoRes) + { + infoRes = config::LookupInfo::FromValues(fc, std::nullopt, version); + if (!infoRes) + { + return score::MakeUnexpected(infoRes.error()); + } + } + infoOpt = *infoRes; + + // create iterator from environment variable if set + if (rootOpt.has_value()) + { + return config::ProcessIterator::FromPathAndInfo(*rootOpt, *infoOpt); + } + } + + // try to read the executable path + // coverity[autosar_cpp14_a18_5_8_violation] The allocation does not lead to a foreseeable safety risk + std::array exeBuffer {}; + span exeSpan { exeBuffer.data(), exeBuffer.size() }; + auto exeSizeRes = utils::ProgramInfo::executablePath(exeSpan); + + // parse result + if (!exeSizeRes) + { + return score::MakeUnexpected(exeSizeRes.error()); + } + std::string_view exePath { exeBuffer.data(), *exeSizeRes }; + if (exePath.empty()) + { + return score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + } + + // go to parent directory + auto lastSlashPos = exePath.find_last_of('/'); + if (lastSlashPos == std::string_view::npos) + { + lastSlashPos = exePath.size(); + } + std::string_view parentPath { exeBuffer.data(), lastSlashPos }; + + // check we can append "/../etc" to parent directory (plus null-terminator) + constexpr std::string_view etc { "/../etc", 7U }; + // coverity[autosar_cpp14_a4_7_1_violation:Intentional] Will not wrap due to program design. + if ((parentPath.size() + etc.size() + 1U) > exeBuffer.size()) + { + // value is too large to store in buffer + FLATCFG_LOG_ERROR() << + "process executable directory path appended with '" << etc << + "' and null-terminated exceeds the size of the buffer (" << + exeBuffer.size() << "), would be path (" << (parentPath.size() + etc.size()) << + "): " << parentPath << etc << '\n'; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // append "/../etc" to parent directory (plus null-terminator) + auto it = std::next(exeBuffer.begin(), static_cast(lastSlashPos)); + // coverity[autosar_cpp14_m5_0_15_violation:Intentional] No arithmetic performed. + it = std::copy(etc.begin(), etc.end(), it); + *it = '\0'; + std::size_t etcSize = static_cast(std::distance(exeBuffer.begin(), it)); + std::string_view etcPath { exeBuffer.data(), etcSize }; + + // create iterator from etc path + return config::ProcessIterator::FromPathAndInfo(etcPath, *infoOpt); +} + +score::Result +FlatCfg::size(const config::Identifier& id) noexcept +{ +#if FLATCFG_INTERNAL_HAS_POSIX + // get file size + auto statRes = utils::Libc::stat(id.path().data()); + if (!statRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, statRes.error().UserMessage()); + } + + // return size + return static_cast(statRes->st_size); +#else + #error Unsupported OS +#endif +} + + +score::Result> +FlatCfg::load(const config::Identifier& id, + std::pmr::polymorphic_allocator alloc) noexcept +{ + // rebind allocator + using AllocVoid = std::pmr::polymorphic_allocator; + using AllocVoidTraits = std::allocator_traits; + using Alloc = AllocVoidTraits::rebind_alloc; + using AllocTraits = AllocVoidTraits::rebind_traits; + Alloc reboundAlloc { alloc }; + +#if FLATCFG_INTERNAL_HAS_POSIX + // open the file + auto fdRes = utils::Libc::open(id.path().data(), O_RDONLY); + if (!fdRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, fdRes.error().UserMessage()); + } + int fd = *fdRes; + + // get file size + auto statRes = utils::Libc::fstat(fd); + if (!statRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, statRes.error().UserMessage()); + } + + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct stat st = *statRes; + + // allocate buffer + unsigned char *ptr = AllocTraits::allocate(reboundAlloc, static_cast(st.st_size)); + if (ptr == nullptr) + { + FLATCFG_LOG_ERROR() << + "failed to allocate " << st.st_size << " bytes"; + return score::MakeUnexpected>(FlatCfgErrorCode::kBadAlloc); + } + + // save into unique ptr here so we don't have to worry about memory leaks + Deleter del { std::move(alloc), static_cast(st.st_size) }; + std::unique_ptr up { ptr, del }; + + // read file into buffer + auto sizeRes = utils::Libc::read(fd, ptr, static_cast(st.st_size)); + if (!sizeRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, sizeRes.error().UserMessage()); + } + std::size_t binSize = *sizeRes; + + // verify binary data + // cheat and do reinterpret_cast with double static_cast to avoid SCA warning + char *binPtr = static_cast(static_cast(ptr)); + std::string_view bin { binPtr, binSize }; + auto verifyRes = utils::FbVerifier::verifyBinary(bin, id.functionCluster(), + id.version().major, + id.version().minor); + if (!verifyRes) + { + return score::MakeUnexpected>(verifyRes.error()); + } + + // success + return up; + +#else + #error Unsupported OS +#endif +} + +} // namespace flatcfg + +FLATCFG_SKIPCOV_END(); diff --git a/src/flatcfg/src/flatcfg/flatcfg_old.cpp b/src/flatcfg/src/flatcfg/flatcfg_old.cpp new file mode 100644 index 0000000..e8f3311 --- /dev/null +++ b/src/flatcfg/src/flatcfg/flatcfg_old.cpp @@ -0,0 +1,313 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include "flatcfg/flatcfg.h" +#include "flatcfg/flatcfg_error.h" +#include "flatcfg/utils/ctc.h" +#include "flatcfg/utils/fb_verifier.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +FLATCFG_SKIPCOV_BEGIN(); + +/// @brief Helper type to hold result of parsing file name. +struct ParseResult { + std::string swclName; + std::optional processName; +}; + +/// @brief Parse mandatory swclName and optional processName from file path. +/// @error FlatCfgErrorCode::kInvalidFileName Returned if the config file's name is invalid. +score::Result parseSwclProcFromFilePath(std::string_view path, std::string_view fcName) noexcept { + using flatcfg::FlatCfgErrorCode; + ParseResult pr; + + // remove everything before (and including) final '/' + auto lastSlashPos = path.rfind('/'); + if (lastSlashPos != std::string_view::npos) { + path.remove_prefix(static_cast(lastSlashPos + 1U)); + } + // check and remove '_flatcfg.bin' suffix + const std::string_view binSuffix{"_flatcfg.bin"}; + if (path.size() < binSuffix.size() || path.substr(path.size() - binSuffix.size()) != binSuffix) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + path.remove_suffix(binSuffix.size()); + + // check and remove fcName prefix + if (path.size() < fcName.size() || path.substr(0U, fcName.size()) != fcName) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + path.remove_prefix(fcName.size()); + + // check and remove swclName suffix (and leading double underscore) + // can't use path.rfind("__"), PWI #68184 + const std::string_view doubleUnderscore{"__"}; + auto lastDoubleUnderscoreIt = + std::find_end(path.begin(), path.end(), doubleUnderscore.begin(), doubleUnderscore.end()); + auto lastDoubleUnderscorePos = std::distance(path.begin(), lastDoubleUnderscoreIt); + if (lastDoubleUnderscoreIt == path.end()) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + // coverity[autosar_cpp14_a4_7_1_violation] Will not overflow, params will never be large enough. + pr.swclName = path.substr(static_cast(lastDoubleUnderscorePos + 2)); + // coverity[autosar_cpp14_a4_7_1_violation] Will not overflow, params will never be large enough. + path.remove_suffix(path.size() - static_cast(lastDoubleUnderscorePos)); + + // ensure that swclName is not empty + if (pr.swclName.empty()) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + + // parse processName if anything is left in the path + if (!path.empty()) { + // check (but DO NOT remove) leading underscore + // don't remove so that our trailing underscore check works if path == '_' + if (path[0U] != '_') { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + + // check that there are no double or trailing underscores + // this also doubles as a check that processName is not empty (except for the leading underscore) + if (path.find("__") != std::string_view::npos || path.back() == '_') { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + + // check that all characters are in [a-z0-9_] + for (char c : path) { + if (!std::islower(c) && !std::isdigit(c) && c != '_') { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidFileName); + } + } + + // save existing processName + path.remove_prefix(1U); // get rid of leading underscore + pr.processName = path; + } + + // return parsed data + return pr; +} + +namespace flatcfg { + +score::Result> FlatCfg::load(const std::string& SwClusterName) noexcept { + std::ifstream infile{}; + std::string filePath{""}; + + { + // Get the path to the configuration file for the requested SWCluster + // clang-format off + // coverity[autosar_cpp14_a23_0_1_violation:FALSE] #54112: An iterator shall not be implicitly converted to const_iterator. + std::map::const_iterator it { static_cast::const_iterator>(m_SwClusterPathMap.find(SwClusterName)) }; + // clang-format on + if (it == m_SwClusterPathMap.cend()) { + return score::MakeUnexpected>(FlatCfgErrorCode::kInvalidSWCLName); + } + + // Create copy because map iterator is locked only in this scope + filePath = it->second; + } + + // Open binary file as input with AtTheEnd flag, to easily get the file size + // RULECHECKER_comment(1, 1, check_enum_usage_overloaded_operator, "Exception for BitmaskType.", false) + infile.open(filePath.data(), std::ios::binary | std::ios::in | std::ifstream::ate); + if (!infile.is_open()) { + return score::MakeUnexpected>(FlatCfgErrorCode::kFileNotFound); + } + + // Get the file size + const size_t fileSize{static_cast(infile.tellg())}; + if (infile.bad()) { + infile.close(); + return score::MakeUnexpected>(FlatCfgErrorCode::kFileAccessError); + } + if (0U == fileSize) { + infile.close(); + return score::MakeUnexpected>(FlatCfgErrorCode::kFileEmpty); + } + + // Seek to beginning prior to read + infile.seekg(0, std::ios::beg); + if (infile.bad()) { + infile.close(); + return score::MakeUnexpected>(FlatCfgErrorCode::kFileAccessError); + } + + // Shared pointer is used to simplify releasing of the allocated memory for the clients + // custom delete ("free") needed for shared pointer because memory was allocated by "malloc" + // on failure, makeSharedNothrow calls free(ptr) and returns nullptr + /* RULECHECKER_comment(2, 1, check_stdlib_use_alloc, "This is necessary for shared_ptr to free the allocated + * memory.", true) */ + std::shared_ptr buffer; + try { + // RULECHECKER_comment(1, 1, check_new_operator, "New required to allocate memory.", true) + buffer = std::shared_ptr(new char[fileSize], std::default_delete()); + } catch (const std::bad_alloc&) { + infile.close(); + return score::MakeUnexpected>(FlatCfgErrorCode::kMemoryAllocationFailed); + } + + infile.read(buffer.get(), static_cast(fileSize)); + if (infile.bad()) { + infile.close(); + return score::MakeUnexpected>(FlatCfgErrorCode::kFileAccessError); + } + + // Ignore error handling for close(), we got what we need + infile.close(); + + // Verify fields required by SCORE are in binary + score::ResultBlank verifyRes{ + utils::FbVerifier::verifyBinary(std::string_view(buffer.get(), fileSize), m_functionClusterNameLowerCase, + m_functionClusterMajorVersion, m_functionClusterMinorVersion)}; + if (!verifyRes) { + return score::MakeUnexpected>(verifyRes.error()); + } + + return buffer; +} + +score::Result> FlatCfg::getSwClusterList() noexcept { + // Check if config path exists + score::Result cfgPath{getConfigPath()}; + + if (!cfgPath) { + return score::MakeUnexpected>(cfgPath.error()); + } + + // Get the list of the fc configuration files in the folder + score::Result> cfgFileList{getConfigFiles(*cfgPath)}; + if (!cfgFileList) { + return score::MakeUnexpected>(cfgFileList.error()); + } + + // Check that only one process name used in the path + return checkFilesAndGetSwClusterList(*cfgFileList); +} + +score::Result FlatCfg::getConfigPath() const noexcept { + std::string configPath{}; + + // Read the environment variable containing the rootFolder for the config for this application + const char* configPathEnv{getenv(envVar_configPath.data())}; + if (configPathEnv == nullptr) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidConfigPath, + (std::string{"Environment variable "} + envVar_configPath.data() + " is not defined.").c_str()); + } + + // Check if path exists + // RULECHECKER_comment(1, 1, check_union_object, "This is the definition provided by the external library.", true) + struct stat buffer; + if (stat(configPathEnv, &buffer) != 0) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidConfigPath, + (std::string{"Path \""} + configPathEnv + "\" not found.").c_str()); + } + + configPath = std::string{configPathEnv}; + + // Append a / if it is not there + if (configPath.back() != '/') { + configPath += "/"; + } + + return configPath; +} + +score::Result> FlatCfg::getConfigFiles(const std::string& configPath) const noexcept { + std::vector fileList{}; + std::string globPattern{configPath}; + globPattern += m_functionClusterNameLowerCase; + globPattern += configFileGlobPattern; + + // Get all files matching the pattern (path/filepattern) + glob_t glob_result; + int glob_ret{glob(globPattern.data(), GLOB_TILDE, NULL, &glob_result)}; + if (glob_ret == 0) { + for (size_t i{0U}; i < glob_result.gl_pathc; ++i) { + // check that the path points to a regular file (rather than e.g. a directory) + /* RULECHECKER_comment(2, 1, check_union_object, "This is the definition provided by the external library. + * ", true) */ + struct stat path_stat; + // *std::next(it, n) is equivalent to it[n] and doesn't trigger an SCA violation + int stat_result{stat(*std::next(glob_result.gl_pathv, static_cast(i)), &path_stat)}; + /* RULECHECKER_comment(2, 1, check_underlying_signedness_conversion, "This is the definition provided by the + * external library.", true) */ + if (stat_result != 0 || !S_ISREG(path_stat.st_mode)) { + continue; + } + + // *std::next(it, n) is equivalent to it[n] and doesn't trigger an SCA violation + fileList.push_back(std::string(*std::next(glob_result.gl_pathv, static_cast(i)))); + } + globfree(&glob_result); + } else if (glob_ret == GLOB_NOMATCH) { // glob failed but don't return error, return empty vector + return {}; + } else { // glob failed + return score::MakeUnexpected>(FlatCfgErrorCode::kConfigFiles); + } + + return fileList; +} + +// coverity[exn_spec_violation:FALSE] Can't exceed swclList.max_size() due to fileList.size() check +score::Result> FlatCfg::checkFilesAndGetSwClusterList(const std::vector& fileList) noexcept { + std::vector swclList{}; + + // Check the fileList isn't bigger than swclList max size so std::length_error can't be thrown + if (fileList.size() > swclList.max_size()) { + return score::MakeUnexpected>(FlatCfgErrorCode::kConfigFiles); + } + + // Clear the map as we are getting new list of files + m_SwClusterPathMap.clear(); + + // Iterate over all filenames in the fileList and get the process name and SWCL + std::optional lastProcName; + for (const std::string& fileName : fileList) { + // parse file name + score::Result prRes = parseSwclProcFromFilePath(fileName, m_functionClusterNameLowerCase); + if (!prRes) { + return score::MakeUnexpected>(prRes.error()); + } + + // check processName + if (!lastProcName) { + lastProcName = prRes->processName; + } + if (prRes->processName != lastProcName) { + // two files with different process names (or one has a process name and one doesn't) + return score::MakeUnexpected>(FlatCfgErrorCode::kMultipleProcessesInPath); + } + + // save swcl if not seen before + if (swclList.end() == std::find(swclList.begin(), swclList.end(), prRes->swclName)) { + swclList.push_back(prRes->swclName); + } + m_SwClusterPathMap[std::move(prRes)->swclName] = fileName; + } + + // Success + return swclList; +} + +// RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "Trivial types do not cause issues here", true) +const std::string_view FlatCfg::configFileGlobPattern = "_*_*_flatcfg[.]bin"; +// RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "Trivial types do not cause issues here", true) +const std::string_view FlatCfg::envVar_configPath = "ECUCFG_ENV_VAR_ROOTFOLDER"; + +} // namespace flatcfg + +FLATCFG_SKIPCOV_END(); diff --git a/src/flatcfg/src/flatcfg/fs/directory/entry.cpp b/src/flatcfg/src/flatcfg/fs/directory/entry.cpp new file mode 100644 index 0000000..66a7935 --- /dev/null +++ b/src/flatcfg/src/flatcfg/fs/directory/entry.cpp @@ -0,0 +1,80 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "flatcfg/fs/directory/entry.h" +#include "flatcfg/utils/logging.h" +#include "flatcfg/flatcfg_error.h" + +#include "score/result/error.h" +#include "score/result/result.h" + +#include +#include + +using score::Result; +using score::ResultBlank; + +namespace flatcfg +{ +namespace fs +{ + +Result +DirectoryEntry::FromView(DirectoryEntryView view) noexcept +{ + ResultBlank res; + DirectoryEntry entry { view, &res }; + if (res) + { + return entry; + } + else + { + return score::MakeUnexpected(res.error()); + } +} + +std::string_view +DirectoryEntry::name() const noexcept +{ + return std::string_view { m_nameBuffer.data(), m_nameSize }; +} + +FileType +DirectoryEntry::type() const noexcept +{ + return m_type; +} + +// coverity[autosar_cpp14_a8_4_10_violation:Intentional] Pointer to mark an out parameter. +DirectoryEntry::DirectoryEntry(DirectoryEntryView view, ResultBlank *resOut) noexcept + : m_nameSize(view.name().size()), + m_type(view.type()) +{ + // check name size + if (m_nameSize > _os::MAX_NAME_LENGTH) + { + FLATCFG_LOG_WARN() << + "directory entry name size (" << view.name().size() << ") exceeds" + " limit of " << _os::MAX_NAME_LENGTH << " bytes: " << view.name(); + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + return; + } + + // copy over name + // character type might be wchar_t and not char on non-posix platforms +#if FLATCFG_INTERNAL_HAS_POSIX + std::ptrdiff_t nameSsize = static_cast(m_nameSize); + const char *namePtrBegin = view.name().data(); + const char *namePtrEnd = std::next(namePtrBegin, nameSsize); + char *namePtrOut = m_nameBuffer.data(); + namePtrOut = std::copy(namePtrBegin, namePtrEnd, namePtrOut); + *namePtrOut = '\0'; +#else + #error Unsupported OS +#endif +} + +} // namespace fs +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/fs/directory/entry_view.cpp b/src/flatcfg/src/flatcfg/fs/directory/entry_view.cpp new file mode 100644 index 0000000..182835e --- /dev/null +++ b/src/flatcfg/src/flatcfg/fs/directory/entry_view.cpp @@ -0,0 +1,109 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "flatcfg/fs/directory/entry_view.h" +#include "flatcfg/utils/logging.h" +#include "flatcfg/flatcfg_error.h" + +#include "score/result/result.h" + +#include + +using score::Result; +using score::ResultBlank; + +namespace flatcfg +{ +namespace fs +{ + +Result +// RULECHECKER_comment(1, 1, check_max_parameters, "4 parameters is acceptable here.", true) +DirectoryEntryView::FromValues(std::uint64_t id, std::uint64_t entrySize, + FileType type, ViewT name) noexcept +{ + ResultBlank res; + DirectoryEntryView entryView { id, entrySize, type, name, &res }; + if (res) + { + return entryView; + } + else + { + return score::MakeUnexpected(res.error()); + } +} + +std::uint64_t +DirectoryEntryView::id() const noexcept +{ + return m_id; +} + +std::uint64_t +DirectoryEntryView::entrySize() const noexcept +{ + return m_entrySize; +} + +FileType +DirectoryEntryView::type() const noexcept +{ + return m_type; +} + +DirectoryEntryView::ViewT +DirectoryEntryView::name() const noexcept +{ + return m_name; +} + +// RULECHECKER_comment(1, 1, check_max_parameters, "5 parameters is acceptable here.", true) +DirectoryEntryView::DirectoryEntryView(std::uint64_t id, std::uint64_t entrySize, + FileType type, ViewT name, +// coverity[autosar_cpp14_a8_4_10_violation:Intentional] Pointer to mark an out parameter. + ResultBlank *resOut) noexcept + : m_id(id), + m_entrySize(entrySize), + m_type(type), + m_name(name) +{ + // check that the name is not empty + // file paths are never empty + if (name.empty()) + { + FLATCFG_LOG_WARN() << + "entry name for entry id " << id << " is empty; paths cannot be" + " empty"; + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + return; + } + + // check that the name does not contain null bytes + // file paths cannot contain null bytes + if (std::find(name.begin(), name.end(), '\0') != name.end()) + { + FLATCFG_LOG_WARN() << + "entry name for entry id " << id << " contains null bytes; paths" + " cannot contain null bytes; entry name (" << name.size() << + "): " << name; + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + return; + } + + // check that the entry size is bigger than the name + // this acts as a secondary check to prevent the entry from acting as a handle to + // heap-allocated memory instead of owning its data + if (entrySize < name.size()) + { + FLATCFG_LOG_WARN() << + "entry size " << entrySize << " for entry id " << id << " is" + " smaller than the entry name (" << name.size() << "): " << name; + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + return; + } +} + +} // namespace fs +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/fs/directory/handle.cpp b/src/flatcfg/src/flatcfg/fs/directory/handle.cpp new file mode 100644 index 0000000..e9b13fd --- /dev/null +++ b/src/flatcfg/src/flatcfg/fs/directory/handle.cpp @@ -0,0 +1,355 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "flatcfg/fs/directory/handle.h" +#include "flatcfg/internal/fs/file/type.h" +#include "flatcfg/utils/libc.h" +#include "flatcfg/utils/logging.h" +#include "flatcfg/flatcfg_error.h" +#include "score/result/result.h" + +#include +#include +#include +#include +#include +#include + +using score::Result; +using score::ResultBlank; +using score::cpp::span; + +namespace flatcfg +{ +namespace fs +{ + +Result +DirectoryHandle::FromPath(std::string_view directoryPath) noexcept +{ + ResultBlank res; + DirectoryHandle handle { directoryPath, &res }; + if (res) + { + return handle; + } + else + { + return score::MakeUnexpected(res.error()); + } +} + +Result +DirectoryHandle::isDirectoryPath(std::string_view path) noexcept +{ +#if FLATCFG_INTERNAL_HAS_POSIX + auto statRes = utils::Libc::lstat(path.data()); + if (!statRes) + { + if (*(statRes.error()) == ENOENT) + { + return false; + } + else + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, statRes.error().UserMessage()); + } + } + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct stat st = *statRes; + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISDIR.", true) + return S_ISDIR(st.st_mode); +#else + #error Unsupported OS +#endif +} + +DirectoryHandle::~DirectoryHandle() noexcept +{ + if (m_handleOpt) + { +#if FLATCFG_INTERNAL_HAS_POSIX + utils::Libc::close(*m_handleOpt); +#else + #error Unsupported OS +#endif + } +} + +DirectoryHandle::DirectoryHandle(DirectoryHandle&& other) noexcept + : m_handleOpt(std::move(other.m_handleOpt)), + m_directoryPath(std::move(other.m_directoryPath)) +{ + // "close" other handle + other.m_handleOpt = std::nullopt; +} + +DirectoryHandle& +DirectoryHandle::operator=(DirectoryHandle&& other) noexcept +{ + // skip self assignment + if (&other == this) + { + return *this; + } + + // steal handle and "close" other handle + m_handleOpt = std::move(other.m_handleOpt); + m_directoryPath = std::move(other.m_directoryPath); + other.m_handleOpt = std::nullopt; + return *this; +} + +Result +DirectoryHandle::readEntriesIntoBuffer(span outputBuffer) const noexcept +{ + // check that we have a handle + if (!m_handleOpt.has_value()) + { + FLATCFG_LOG_WARN() << + "cannot read entries from a closed directory handle"; + return score::MakeUnexpected(FlatCfgErrorCode::kHandleClosed); + } + + // limit buffer size to INT_MAX + // don't want to this to exceed the value of ssize_t, and since that's + // platform dependent, just use int as a stand-in + constexpr int intMax = std::numeric_limits::max(); + const auto bufferSize = std::min(outputBuffer.size(), static_cast(intMax)); + + // read entries +#if FLATCFG_INTERNAL_HAS_LINUX + auto sizeRes = utils::Libc::sys_getdents64(*m_handleOpt, outputBuffer.data(), bufferSize); +#elif FLATCFG_INTERNAL_HAS_QNX + // RULECHECKER_comment(1, 1, check_enum_usage, "Required by OS.", true) + constexpr unsigned int xtype = static_cast(_IO_XTYPE_READDIR | _IO_XFLAG_DIR_STAT_FORM_T64_2008); + auto sizeRes = utils::Libc::_readx(*m_handleOpt, outputBuffer.data(), bufferSize, xtype, nullptr, 0U); +#elif FLATCFG_INTERNAL_HAS_MACOS + long unusedBaseP {}; + auto sizeRes = utils::Libc::sys_getdirentries64(*m_handleOpt, outputBuffer.data(), bufferSize, &unusedBaseP); +#else + #error Unsupported OS +#endif + + // return number of bytes read + if (!sizeRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, sizeRes.error().UserMessage()); + } + std::size_t bytesRead = *sizeRes; + return static_cast(bytesRead); +} + +Result +DirectoryHandle::parseEntryFromBuffer(span buffer) const noexcept +{ + // check that we have a handle + if (!m_handleOpt.has_value()) + { + FLATCFG_LOG_WARN() << + "cannot parse entries with a closed directory handle"; + return score::MakeUnexpected(FlatCfgErrorCode::kHandleClosed); + } + + // check that the buffer can hold a complete entry (except the name) +#if FLATCFG_INTERNAL_HAS_POSIX + // +2 for smallest possible null-terminated name + constexpr std::size_t minEntrySize = offsetof(_os::DirEntryKernelT, d_name) + 2U; +#else + #error Unsupported OS +#endif + if (buffer.size() < minEntrySize) + { + FLATCFG_LOG_WARN() << + "buffer size (" << buffer.size() << ") is smaller than the minimum" + " size of an entry (" << minEntrySize << ")"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // parse file identifier +#if FLATCFG_INTERNAL_HAS_POSIX + decltype(_os::DirEntryKernelT::d_ino) ino {}; + const char *inoPtr = std::next(buffer.data(), static_cast(offsetof(_os::DirEntryKernelT, d_ino))); +#else + #error Unsupported OS +#endif + std::memcpy(&ino, inoPtr, sizeof(ino)); + std::uint64_t id = static_cast(ino); + + // parse entry size +#if FLATCFG_INTERNAL_HAS_POSIX + decltype(_os::DirEntryKernelT::d_reclen) reclen {}; + const char *recPtr = std::next(buffer.data(), static_cast(offsetof(_os::DirEntryKernelT, d_reclen))); +#else + #error Unsupported OS +#endif + std::memcpy(&reclen, recPtr, sizeof(reclen)); + std::uint64_t entrySize = static_cast(reclen); + + // parse file name size +#if FLATCFG_INTERNAL_HAS_LINUX + // need to manually check size because reclen may include alignment padding + const std::uint64_t remainingEntrySize = static_cast( + static_cast(entrySize) - + static_cast(offsetof(_os::DirEntryKernelT, d_name))); + const std::uint64_t remainingBufferSize = + buffer.size() - offsetof(_os::DirEntryKernelT, d_name); + const auto nameSizeUpperBound = std::min( + remainingEntrySize, remainingBufferSize); + auto nameBegin = std::next( + buffer.data(), + static_cast(offsetof(_os::DirEntryKernelT, d_name))); + auto nameEnd = std::next( + nameBegin, static_cast(nameSizeUpperBound)); + auto nullIt = std::find(nameBegin, nameEnd, '\0'); + if (nullIt == nameEnd) + { + std::string_view nameStart { nameBegin, nameSizeUpperBound }; + FLATCFG_LOG_WARN() << "entry name for entry id " << id + << " with entry size " << entrySize + << " extends past end of entry or is not " + "null-terminated;" + " name starts with: " + << nameStart; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + auto nameSize = std::distance(nameBegin, nullIt); +#elif FLATCFG_INTERNAL_HAS_QNX + decltype(_os::DirEntryKernelT::d_namelen) nameSize {}; + const char *namelenPtr = std::next(buffer.data(), static_cast(offsetof(_os::DirEntryKernelT, d_namelen))); + std::memcpy(&nameSize, namelenPtr, sizeof(nameSize)); +#elif FLATCFG_INTERNAL_HAS_MACOS + decltype(_os::DirEntryKernelT::d_namlen) nameSize {}; + const char *namelenPtr = std::next(buffer.data(), static_cast(offsetof(_os::DirEntryKernelT, d_namlen))); + std::memcpy(&nameSize, namelenPtr, sizeof(nameSize)); +#else + #error Unsupported OS +#endif + + // parse file name +#if FLATCFG_INTERNAL_HAS_POSIX + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is correct.", true) + const char *namePtr = std::next(buffer.data(), static_cast(offsetof(_os::DirEntryKernelT, d_name))); +#else + #error Unsupported OS +#endif + auto name = std::string_view { namePtr, static_cast(nameSize) }; + + // check that string is not empty + if (name.empty()) + { + FLATCFG_LOG_WARN() << + "entry name for entry id " << id << " is empty; paths cannot be" + " empty"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + } + + // check that the entry is big enough to hold the whole name including null-terminator + // this is an idiot check, do not rely on this for correctness + if (entrySize < name.size()) + { + FLATCFG_LOG_WARN() << + "entry size " << entrySize << " for entry id " << id << " is" + " smaller than the entry name (" << name.size() << "): " << name; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // check that the buffer actually holds the whole name including null-terminator + // DO NOT create a pointer that points outside the buffer (undefined behaviour) + auto bytesRemaining = buffer.size(); + bytesRemaining -= static_cast(std::distance(buffer.data(), name.data())); + // size doesn't include required null-terminator + if (bytesRemaining < (name.size() + 1U)) + { + FLATCFG_LOG_WARN() << + "entry name extends past end of buffer; buffer has " << + bytesRemaining << " bytes but null-terminated entry name would" + " require: " << (name.size() + 1U) << " bytes"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // check that the name does not contain a null byte + if (std::find(name.begin(), name.end(), '\0') != name.end()) + { + FLATCFG_LOG_WARN() << + "entry name for id " << id << " contains null bytes; paths cannot" + " contain null bytes; entry name (" << name.size() << "): " << name; + return score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + } + + // check that the name is null-terminated + // this is safe because we checked above that the buffer extends at least + // far enough to hold a null terminator + std::ptrdiff_t nameSsize = static_cast(name.size()); + const char *it = std::next(name.data(), nameSsize); + if (*it != '\0') + { + FLATCFG_LOG_WARN() << + "entry name for id " << id << " is not null-terminated; entry" + " name (" << name.size() << "): " << name; + return score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + } + + // parse file type +#if FLATCFG_INTERNAL_HAS_LINUX || FLATCFG_INTERNAL_HAS_MACOS + decltype(_os::DirEntryKernelT::d_type) type {}; + const char *typePtr = std::next(buffer.data(), static_cast(offsetof(_os::DirEntryKernelT, d_type))); + std::memcpy(&type, typePtr, sizeof(type)); + FileType fileType = fileTypeFromDirentType(type); +#elif FLATCFG_INTERNAL_HAS_QNX + // TODO: qnx needs stat or dirent_extra + // annoyingly qnx does not support fstatat properly + // fall back to lstat + // cannot lstat "." or ".." + auto fileType = FileType::DIRECTORY; + if (name != "." && name != "..") + { + std::string path = m_directoryPath; + if (!path.empty() && path.back() != '/') + { + path += '/'; + } + + path += name; + auto statRes = utils::Libc::lstat(path.c_str()); + if (!statRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, statRes.error().UserMessage()); + } + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct stat st = *statRes; + fileType = fileTypeFromStatMode(st.st_mode); + } +#else + #error Unsupported OS +#endif + + // success + return DirectoryEntryView::FromValues(id, entrySize, fileType, name); +} + +// coverity[autosar_cpp14_a8_4_10_violation:Intentional] Pointer to mark an out parameter. +DirectoryHandle::DirectoryHandle(std::string_view directoryPath, ResultBlank *resOut) noexcept + : m_directoryPath(directoryPath) +{ +#if FLATCFG_INTERNAL_HAS_POSIX + Result fdRes {}; +#if FLATCFG_INTERNAL_HAS_QNX + fdRes = utils::Libc::open(directoryPath.data(), O_RDONLY | O_EXCL | O_LARGEFILE); +#else + fdRes = utils::Libc::open(directoryPath.data(), O_RDONLY | O_DIRECTORY); +#endif + if (!fdRes) + { + *resOut = score::MakeUnexpected(FlatCfgErrorCode::kOsError, fdRes.error().UserMessage()); + return; + } + m_handleOpt = *fdRes; +#else + #error Unsupported OS +#endif +} + +} // namespace fs +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/fs/directory/iterator.cpp b/src/flatcfg/src/flatcfg/fs/directory/iterator.cpp new file mode 100644 index 0000000..0259450 --- /dev/null +++ b/src/flatcfg/src/flatcfg/fs/directory/iterator.cpp @@ -0,0 +1,346 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "flatcfg/fs/directory/iterator.h" +#include "flatcfg/utils/ctc.h" +#include "flatcfg/utils/logging.h" +#include "flatcfg/flatcfg_error.h" + +#include "score/time/common/Abort.h" + +#include +#include + +using score::cpp::span; +using score::time::common::logFatalAndAbort; + +FLATCFG_SKIPCOV_BEGIN(); + +namespace flatcfg +{ +namespace fs +{ + +score::Result +DirectoryIterator::FromPath(std::string_view directoryPath) noexcept +{ + score::ResultBlank res; + DirectoryIterator it { directoryPath, &res }; + if (res) + { + return it; + } + else + { + return score::MakeUnexpected(res.error()); + } +} + +DirectoryIterator::DirectoryIterator() noexcept = default; +DirectoryIterator::DirectoryIterator(DirectoryIterator&& other) noexcept = default; +DirectoryIterator& DirectoryIterator::operator=(DirectoryIterator&& other) noexcept = default; + +DirectoryIterator& +DirectoryIterator::operator++() noexcept +{ + // undefined behaviour to increment an iterator in error state + FLATCFG_SKIPCOV_BEGIN(); + if (isError()) + { + logFatalAndAbort("Incremented flatcfg::fs::DirectoryIterator in error state"); + } + FLATCFG_SKIPCOV_END(); + + // skip if we're a sentinel + if (isSentinel()) + { + return *this; + } + + // set position to next entry, or the end of the buffer + auto oldEntryViewRes = getCurrentEntryView(); + if (!oldEntryViewRes) + { + m_handleOptRes = score::MakeUnexpected>(oldEntryViewRes.error()); + return *this; + } + DirectoryEntryView oldEntryView = *oldEntryViewRes; + // coverity[autosar_cpp14_a4_7_1_violation:Intentional] Will not wrap due to program design. + m_bufferPos += oldEntryView.entrySize(); + + // make sure the next entry is valid (or become a sentinel) + do { + + // if we finished our buffer, refill it + if (m_bufferPos == m_bufferSize) + { + // read data from our handle into tthe buffer + span span { m_buffer.data(), m_buffer.size() }; + auto refillRes = (*m_handleOptRes)->readEntriesIntoBuffer(span); + if (!refillRes) + { + m_handleOptRes = score::MakeUnexpected>(refillRes.error()); + return *this; + } + m_bufferPos = 0U; + m_bufferSize = *refillRes; + } + + // if no data was read, there's no entries left, so we become a sentinel + if (m_bufferSize == 0U) + { + m_handleOptRes = std::nullopt; + return *this; + } + + // skip invalid entries in the current remaining buffer + auto skipRes = skipInvalidEntries(); + if (!skipRes) + { + m_handleOptRes = score::MakeUnexpected>(skipRes.error()); + return *this; + } + } + // all entries in the buffer were invalid and we need a new buffer + while (m_bufferPos == m_bufferSize); + + // we either got a valid entry, became a sentinel, or encountered an error + return *this; +} + +void +DirectoryIterator::operator++(int) noexcept +{ + // better to have the abort here for easier debugging + // undefined behaviour to increment an iterator in error state + FLATCFG_SKIPCOV_BEGIN(); + if (isError()) + { + logFatalAndAbort("Incremented flatcfg::fs::DirectoryIterator in error state"); + } + FLATCFG_SKIPCOV_END(); + + // do increment + ++(*this); +} + +DirectoryIterator::value_type +DirectoryIterator::operator*() noexcept +{ + if (!m_handleOptRes) + { + // propagate error + return score::MakeUnexpected(m_handleOptRes.error()); + } + else if (!m_handleOptRes->has_value()) + { + // undefined behaviour to dereference a sentinel iterator + FLATCFG_SKIPCOV_BEGIN(); + logFatalAndAbort("Dereferenced sentinel flatcfg::fs::DirectoryIterator"); + FLATCFG_SKIPCOV_END(); + } + else + { + // parse current entry view + auto entryViewRes = getCurrentEntryView(); + if (!entryViewRes) + { + m_handleOptRes = score::MakeUnexpected>(entryViewRes.error()); + return score::MakeUnexpected(entryViewRes.error()); + } + DirectoryEntryView entryView = *entryViewRes; + + // convert entry view to entry and return + auto entryRes = DirectoryEntry::FromView(entryView); + if (!entryRes) + { + m_handleOptRes = score::MakeUnexpected>(entryRes.error()); + return score::MakeUnexpected(entryRes.error()); + } + + // success + return *entryRes; + } +} + +bool +DirectoryIterator::isError() const noexcept +{ + return !m_handleOptRes; +} + +bool +DirectoryIterator::isSentinel() const noexcept +{ + return m_handleOptRes && !m_handleOptRes->has_value(); +} + +// RULECHECKER_comment(1, 1, check_non_explicit_conversion_function, "Non-explicit for easier usage.", true) +DirectoryIterator::operator bool() const noexcept +{ + return !(isError() || isSentinel()); +} + +// coverity[autosar_cpp14_a8_4_10_violation:Intentional] Pointer to mark an out parameter. +DirectoryIterator::DirectoryIterator(std::string_view directoryPath, score::ResultBlank *resOut) noexcept +{ + // open the directory handle + auto handleRes = DirectoryHandle::FromPath(directoryPath); + if (!handleRes) + { + *resOut = score::MakeUnexpected(handleRes.error()); + return; + } + m_handleOptRes = *(std::move(handleRes)); + + // make sure the next (a.k.a. first) entry is valid (or become a sentinel) + do { + + // if we finished our buffer, refill it + if (m_bufferPos == m_bufferSize) + { + // read data from handle into buffer + span span { m_buffer.data(), m_buffer.size() }; + auto refillRes = (*m_handleOptRes)->readEntriesIntoBuffer(span); + if (!refillRes) + { + *resOut = score::MakeUnexpected(refillRes.error()); + return; + } + m_bufferPos = 0U; + m_bufferSize = *refillRes; + } + + // if no data was read, there's no entries left so we become a sentinel + if (m_bufferSize == 0U) + { + m_handleOptRes = std::nullopt; + return; + } + + // skip invalid entries in the current remaining buffer + auto skipRes = skipInvalidEntries(); + if (!skipRes) + { + *resOut = score::MakeUnexpected(skipRes.error()); + return; + } + } + // all entries in the buffer were invalid and we need a new buffer + while (m_bufferPos == m_bufferSize); + + // we either got a valid entry, or became a sentinel + // if we encountered an error, we would have returned immediately +} + +score::Result +DirectoryIterator::getCurrentEntryView() const noexcept +{ + // create span from remaining buffer + std::ptrdiff_t bufferSpos = static_cast(m_bufferPos); + const char *ptr = std::next(m_buffer.data(), bufferSpos); + // coverity[autosar_cpp14_a4_7_1_violation:Intentional] Will not wrap due to program design. + std::size_t size = m_bufferSize - m_bufferPos; + span span { ptr, size }; + + // create entry view + auto entryViewRes = (*m_handleOptRes)->parseEntryFromBuffer(span); + if (!entryViewRes) + { + return score::MakeUnexpected(entryViewRes.error()); + } + else + { + return *entryViewRes; + } +} + +score::ResultBlank +DirectoryIterator::skipInvalidEntries() noexcept +{ + // go through all our entries + while (m_handleOptRes && + m_handleOptRes->has_value() && + (m_bufferPos < m_bufferSize)) + { + // get the current entry + auto entryViewRes = getCurrentEntryView(); + if (!entryViewRes) + { + return score::MakeUnexpected(entryViewRes.error()); + } + DirectoryEntryView entryView = *entryViewRes; + + // break if the entry is valid + // an id of 0 signifies an invalid or deleted entry + if (entryView.id() != 0U) + { + break; + } + + // skip to next entry + m_bufferPos += entryView.entrySize(); + } + + // found valid entry or skipped all entries in buffer + return {}; +} + +bool +operator==(const DirectoryIterator& lhs, const DirectoryIterator& rhs) noexcept +{ + // an iterator always compares equal to itself, even if it's an error + if (&lhs == &rhs) + { + return true; + } + + // errors always compare unequal to everything + if (!lhs.m_handleOptRes || !rhs.m_handleOptRes) + { + return false; + } + + // sentinels always compare equal to other sentinels + if (!lhs.m_handleOptRes->has_value() && !rhs.m_handleOptRes->has_value()) + { + return true; + } + + // sentinels always compare unequal to non-sentinels + if (!lhs.m_handleOptRes->has_value() || !rhs.m_handleOptRes->has_value()) + { + return false; + } + + // parse entry views + auto lhsEntryViewRes = lhs.getCurrentEntryView(); + auto rhsEntryViewRes = rhs.getCurrentEntryView(); + + // any error in the parsing compares unequal + if (!lhsEntryViewRes || !rhsEntryViewRes) + { + return false; + } + + // get values from results + DirectoryEntryView lhsEntryView = *lhsEntryViewRes; + DirectoryEntryView rhsEntryView = *rhsEntryViewRes; + + // compare members + const bool sameId = lhsEntryView.id() == rhsEntryView.id(); + const bool sameName = lhsEntryView.name() == rhsEntryView.name(); + return sameId && sameName; +} + +bool +operator!=(const DirectoryIterator& lhs, const DirectoryIterator& rhs) noexcept +{ + return !(lhs == rhs); +} + +} // namespace fs +} // namespace flatcfg + +FLATCFG_SKIPCOV_END(); diff --git a/src/flatcfg/src/flatcfg/fs/file/type.cpp b/src/flatcfg/src/flatcfg/fs/file/type.cpp new file mode 100644 index 0000000..a14dbd4 --- /dev/null +++ b/src/flatcfg/src/flatcfg/fs/file/type.cpp @@ -0,0 +1,115 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "flatcfg/fs/_os.h" +#include "flatcfg/internal/fs/file/type.h" +#include "flatcfg/utils/logging.h" + +#if FLATCFG_INTERNAL_HAS_POSIX + #include +#endif + +#if FLATCFG_INTERNAL_HAS_LINUX || FLATCFG_INTERNAL_HAS_MACOS + #include +#endif + +namespace flatcfg +{ +namespace fs +{ + +FileType +fileTypeFromDirentType(unsigned int dtype) noexcept +{ +#if FLATCFG_INTERNAL_HAS_LINUX || FLATCFG_INTERNAL_HAS_MACOS + switch (dtype) + { + case DT_REG: + return FileType::REGULAR; + case DT_DIR: + return FileType::DIRECTORY; + case DT_CHR: + return FileType::CHARACTER; + case DT_BLK: + return FileType::BLOCK; + case DT_FIFO: + return FileType::FIFO; + case DT_LNK: + return FileType::SYMLINK; + case DT_SOCK: + return FileType::SOCKET; + default: + { + FLATCFG_LOG_DEBUG() << + "struct dirent.d_type value " << dtype << " does not match any" + " known file type constant"; + return FileType::UNKNOWN; + } + } +#else + FLATCFG_LOG_ERROR() << + "attempting to treat dtype value " << dtype << " as if it came from" + " struct dirent.d_type on a platform which either does not support this" + " field or does not support dirent"; + return FileType::UNKNOWN; +#endif +} + +FileType +// RULECHECKER_comment(1, 1, check_max_control_nesting_depth, "Checking all enum labels.", true) +fileTypeFromStatMode(unsigned int mode) noexcept +{ +#if FLATCFG_INTERNAL_HAS_POSIX + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISREG.", true) + if (S_ISREG(mode)) + { + return FileType::REGULAR; + } + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISDIR.", true) + else if (S_ISDIR(mode)) + { + return FileType::DIRECTORY; + } + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISCHR.", true) + else if (S_ISCHR(mode)) + { + return FileType::CHARACTER; + } + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISBLK.", true) + else if (S_ISBLK(mode)) + { + return FileType::BLOCK; + } + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISFIFO.", true) + else if (S_ISFIFO(mode)) + { + return FileType::FIFO; + } + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISLINK.", true) + else if (S_ISLNK(mode)) + { + return FileType::SYMLINK; + } + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Conversion is part of S_ISSOCK.", true) + else if (S_ISSOCK(mode)) + { + return FileType::SOCKET; + } + else + { + FLATCFG_LOG_DEBUG() << + "struct stat.st_mode value " << mode << " is not set to any known" + " file type"; + return FileType::UNKNOWN; + } +#else + FLATCFG_LOG_ERROR() << + "attempting to treat mode value " << mode << " as of it came from" + " struct stat.st_mode on a platform which does not support stat"; + return FileType::UNKNOWN; +#endif +} + +} // namespace fs +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/internal/fs/file/type.h b/src/flatcfg/src/flatcfg/internal/fs/file/type.h new file mode 100644 index 0000000..7309ce0 --- /dev/null +++ b/src/flatcfg/src/flatcfg/internal/fs/file/type.h @@ -0,0 +1,26 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_INTERNAL_FS_FILE_TYPE_H +#define FLATCFG_INTERNAL_FS_FILE_TYPE_H + +#include "flatcfg/fs/file/type.h" + +namespace flatcfg +{ +namespace fs +{ + +/// @brief Convert the value of a dirent.d_type to FileType. +FileType +fileTypeFromDirentType(unsigned int dtype) noexcept; + +/// @brief Convert the value of a stat.st_mode to FileType. +FileType +fileTypeFromStatMode(unsigned int mode) noexcept; + +} // namespace fs +} // namespace flatcfg + +#endif // FLATCFG_INTERNAL_FS_FILE_TYPE_H diff --git a/src/flatcfg/src/flatcfg/log/common.h b/src/flatcfg/src/flatcfg/log/common.h new file mode 100644 index 0000000..455eecb --- /dev/null +++ b/src/flatcfg/src/flatcfg/log/common.h @@ -0,0 +1,55 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_LOG_COMMON_H +#define FLATCFG_LOG_COMMON_H + +#include + +namespace flatcfg +{ +namespace log +{ + +/// @brief Equivalent to score::mw::log::LogLevel. +enum class LogLevel : std::uint8_t +{ + /// @brief No logging. + kOff, + + /// @brief Fatal error, not recoverable. + kFatal, + + /// @brief Error with impact to correct functionality. + kError, + + /// @brief Warning if correct behaviour cannot be ensured. + kWarn, + + /// @brief Informational, providing high level understanding. + kInfo, + + /// @brief Detailed information for programmers. + kDebug, + + /// @brief Extra-verbose debug messages (highest grade of information). + kVerbose +}; + +/// @brief Mapping of log level label to string. +// coverity[autosar_cpp14_m3_4_1_violation] This has to be defined here by design. +constexpr const char *LogLevelNames[] = { + "kOff", + "kFatal", + "kError", + "kWarn", + "kInfo", + "kDebug", + "kVerbose" +}; + +} // namespace log +} // namespace flatcfg + +#endif // FLATCFG_LOG_COMMON_H diff --git a/src/flatcfg/src/flatcfg/log/log_stream.cpp b/src/flatcfg/src/flatcfg/log/log_stream.cpp new file mode 100644 index 0000000..cd744f6 --- /dev/null +++ b/src/flatcfg/src/flatcfg/log/log_stream.cpp @@ -0,0 +1,284 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "log_stream.h" +#include "flatcfg/utils/ctc.h" +#include "flatcfg/utils/libc.h" + +#include +#include +#include +#include + +#include "score/time/common/Abort.h" + +namespace +{ + +using flatcfg::fs::_os::FileHandleT; +using score::time::common::logFatalAndAbort; + +/// @brief Get the file handle for stdout. +FileHandleT +// coverity[autosar_cpp14_a0_1_3_violation] This method is being used in this module +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +openStdout() noexcept +{ +#if FLATCFG_INTERNAL_HAS_POSIX + auto fdRes = flatcfg::utils::Libc::fileno(stdout); + FLATCFG_SKIPCOV_BEGIN(); + if (!fdRes) + { + logFatalAndAbort("Could not obtain underlying file descriptor from stdout"); + } + FLATCFG_SKIPCOV_END(); + return fdRes.value(); +#else + #error Unsupported OS +#endif +} + +/// @brief Write data to a file handle. +void +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +writeToHandle(FileHandleT file, const void *buf, std::size_t count) noexcept +{ +#if FLATCFG_INTERNAL_HAS_POSIX + static_cast(flatcfg::utils::Libc::write(file, buf, count)); +#else + #error Unsupported OS +#endif +} + +} // namespace + +namespace flatcfg +{ +namespace log +{ + +LogStream::LogStream(LogLevel logLevel) noexcept +{ + // only try to get stdout if we will actually log something + if (logLevel != LogLevel::kOff) + { + m_handleOpt = openStdout(); + } +} + +LogStream::~LogStream() noexcept +{ + *this << '\n'; + if (m_handleOpt.has_value()) + { + writeToHandle(*m_handleOpt, m_buffer.data(), m_pos); + } +} + +LogStream& +LogStream::operator<<(bool value) noexcept +{ + writeToBuffer(value ? "true" : "false"); + return *this; +} + +LogStream& +LogStream::operator<<(unsigned char value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(unsigned short value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(unsigned int value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +LogStream::operator<<(unsigned long value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(unsigned long long value) noexcept +{ + // +2: 1 for null-terminator, 1 because log10(2) in digits10 rounds down + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Fine because issue would be caught at compile time.", true) + char buf[std::numeric_limits::digits10 + 2U]; + + // cannot fail + // inputs are restricted to known values, so EILSEQ, EINVAL, and SIGSEGV + // would be caught in tests + int res = std::snprintf(buf, sizeof(buf), "%llu", value); + + // coverity[cert_err33_c_violation:Intentional] Issue for core-types. + std::string_view view { buf, static_cast(res) }; + writeToBuffer(view); + return *this; +} + +LogStream& +LogStream::operator<<(signed char value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(signed short value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +LogStream::operator<<(signed int value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(signed long value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(signed long long value) noexcept +{ + // +3: 1 for null-terminator, 1 because log10(2) in digits10 rounds down, + // 1 for minus sign when negative + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Fine because issue would be caught at compile time.", true) + char buf[std::numeric_limits::digits10 + 3U]; + + // cannot fail + // inputs are restricted to known values, so EILSEQ, EINVAL, and SIGSEGV + // would be caught in tests + int res = std::snprintf(buf, sizeof(buf), "%lld", value); + + // coverity[cert_err33_c_violation:Intentional] Issue for core-types. + std::string_view view { buf, static_cast(res) }; + writeToBuffer(view); + return *this; +} + +LogStream& +LogStream::operator<<(float value) noexcept +{ + return this->operator<<(static_cast(value)); +} + +LogStream& +LogStream::operator<<(double value) noexcept +{ + // floating point types are difficult + // be generous and test limits + char buf[16U] {}; + + // cannot fail + // inputs are restricted to known values, so EILSEQ, EINVAL, and SIGSEGV + // would be caught in tests + int res = std::snprintf(buf, sizeof(buf), "%e", value); + + // coverity[cert_err33_c_violation:Intentional] Issue for core-types. + std::string_view view { buf, static_cast(res) }; + writeToBuffer(view); + return *this; +} + +LogStream& +LogStream::operator<<(const void *value) noexcept +{ + // +2: for 0x prefix + // +2: 1 for null-terminator, 1 because log10(2) in digits10 rounds down + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "Fine because issue would be caught at compile time.", true) + char buf[std::numeric_limits::digits10 + 4U]; + // RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "Required because we need pointer's address value.", true) + unsigned long long addr = static_cast(reinterpret_cast(value)); + + // cannot fail + // inputs are restricted to known values, so EILSEQ, EINVAL, and SIGSEGV + // would be caught in tests + int res = std::snprintf(buf, sizeof(buf), "0x%llx", addr); + + // coverity[cert_err33_c_violation:Intentional] Issue for core-types. + std::string_view view { buf, static_cast(res) }; + writeToBuffer(view); + return *this; +} + +LogStream& +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +LogStream::operator<<(const char *value) noexcept +{ + writeToBuffer(value); + return *this; +} + +LogStream& +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +LogStream::operator<<(char value) noexcept +{ + std::string_view view { &value, 1u }; + writeToBuffer(view); + return *this; +} + +LogStream& +LogStream::operator<<(std::string_view value) noexcept +{ + writeToBuffer(value); + return *this; +} + +LogStream& +LogStream::operator<<(const score::result::Error& value) noexcept +{ + writeToBuffer(value.Message().data()); + return *this; +} + +void +LogStream::writeToBuffer(std::string_view data) noexcept +{ + // skip if no handle (because LogLevel is kOff) + if (!m_handleOpt.has_value()) + { + return; + } + + // write all data + while (!data.empty()) + { + // flush buffer if full + if (m_pos == m_buffer.size()) + { + writeToHandle(*m_handleOpt, m_buffer.data(), m_buffer.size()); + m_pos = 0u; + } + + // get data we will write + std::size_t bufRemaining = m_buffer.size() - m_pos; + std::size_t writeSize = std::min(data.size(), bufRemaining); + std::string_view dataToWrite = data.substr(0U, writeSize); + + // write as much data as fits in buffer + auto it = std::next(m_buffer.data(), static_cast(m_pos)); + std::copy(dataToWrite.begin(), dataToWrite.end(), it); + + // update variables + data.remove_prefix(writeSize); + m_pos += writeSize; + } +} + +} // namespace log +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/log/log_stream.h b/src/flatcfg/src/flatcfg/log/log_stream.h new file mode 100644 index 0000000..32fbf0b --- /dev/null +++ b/src/flatcfg/src/flatcfg/log/log_stream.h @@ -0,0 +1,132 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_LOG_LOG_STREAM_H +#define FLATCFG_LOG_LOG_STREAM_H + +#include "common.h" + +#include "flatcfg/fs/_os.h" + +#include "score/result/error.h" + +#include +#include +#include +#include + +namespace flatcfg +{ +namespace log +{ + +/// @brief Equivalent to score::mw::log::LogStream but with zero allocations. +class LogStream +{ +public: + /// @brief Construct a stream. + LogStream(LogLevel logLevel) noexcept; + + /// @brief Destructor. + ~LogStream() noexcept; + + /// @brief Move (but not copy) constructible. + LogStream(LogStream&&) = default; + + /// @brief Move (but not copy) assignable + LogStream& + operator=(LogStream&&) = default; + + /// @brief Log bool. + LogStream& + operator<<(bool value) noexcept; + + /// @brief Log unsigned char. + LogStream& + operator<<(unsigned char value) noexcept; + + /// @brief Log unsigned short. + LogStream& + operator<<(unsigned short value) noexcept; + + /// @brief Log unsigned int. + LogStream& + operator<<(unsigned int value) noexcept; + + /// @brief Log unsigned long. + LogStream& + operator<<(unsigned long value) noexcept; + + /// @brief Log unsigned long long. + LogStream& + operator<<(unsigned long long value) noexcept; + + /// @brief Log signed char. + LogStream& + operator<<(signed char value) noexcept; + + /// @brief Log signed short. + LogStream& + operator<<(signed short value) noexcept; + + /// @brief Log signed int. + LogStream& + operator<<(signed int value) noexcept; + + /// @brief Log signed long. + LogStream& + operator<<(signed long value) noexcept; + + /// @brief Log signed long long. + LogStream& + operator<<(signed long long value) noexcept; + + /// @brief Log float. + LogStream& + operator<<(float value) noexcept; + + /// @brief Log double. + LogStream& + operator<<(double value) noexcept; + + /// @brief Log memory address. + LogStream& + operator<<(const void *value) noexcept; + + /// @brief Log null-terminated C string. + LogStream& + operator<<(const char *value) noexcept; + + /// @brief Log a character. + LogStream& + operator<<(char value) noexcept; + + /// @brief Log std::string_view. + LogStream& + operator<<(std::string_view value) noexcept; + + /// @brief Log ErrorCode. + LogStream& + operator<<(const score::result::Error& value) noexcept; + +private: + /// @brief Write data to buffer. If buffer is full, write to file handle + /// and refill. + void + writeToBuffer(std::string_view data) noexcept; + + /// @brief Write buffer. + std::array m_buffer {}; + + /// @brief Position in write buffer. + std::size_t m_pos {}; + + /// @brief File handle to which to write logs. + std::optional m_handleOpt {}; +}; + +} // namespace log +} // namespace flatcfg + +#endif // FLATCFG_LOG_LOG_STREAM_H diff --git a/src/flatcfg/src/flatcfg/log/logger.cpp b/src/flatcfg/src/flatcfg/log/logger.cpp new file mode 100644 index 0000000..1e0c8c1 --- /dev/null +++ b/src/flatcfg/src/flatcfg/log/logger.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "logger.h" + +#include +#include + +namespace flatcfg +{ +namespace log +{ + +Logger::Logger(std::string_view name, LogLevel logLevel) noexcept + : m_logLevel(logLevel) +{ + // copy name, possibly truncating it + if (name.size() > m_nameBuf.size()) + { + name.remove_suffix(name.size() - m_nameBuf.size()); + } + auto it = std::copy(name.begin(), name.end(), m_nameBuf.begin()); + m_nameSize = static_cast(std::distance(m_nameBuf.begin(), it)); +} + +LogStream +Logger::LogFatal() noexcept +{ + return LogAt(LogLevel::kFatal); +} + +LogStream +Logger::LogError() noexcept +{ + return LogAt(LogLevel::kError); +} + +LogStream +Logger::LogWarn() noexcept +{ + return LogAt(LogLevel::kWarn); +} + +LogStream +Logger::LogInfo() noexcept +{ + return LogAt(LogLevel::kInfo); +} + +LogStream +Logger::LogDebug() noexcept +{ + return LogAt(LogLevel::kDebug); +} + +LogStream +// coverity[autosar_cpp14_a7_5_2_violation:Intentional] False positive. +Logger::LogVerbose() noexcept +{ + return LogAt(LogLevel::kVerbose); +} + +LogStream +Logger::LogAt(LogLevel logLevel) noexcept +{ + // figure out log level + if (m_logLevel == LogLevel::kOff || + static_cast(m_logLevel) < static_cast(logLevel)) + { + logLevel = LogLevel::kOff; + } + + // log prelude + LogStream stream { logLevel }; + std::string_view name { m_nameBuf.data(), m_nameSize }; + stream << "[" << name << "][" + << LogLevelNames[static_cast(logLevel)] << "] "; + + // return + return stream; +} + +} // namespace log +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/log/logger.h b/src/flatcfg/src/flatcfg/log/logger.h new file mode 100644 index 0000000..524d435 --- /dev/null +++ b/src/flatcfg/src/flatcfg/log/logger.h @@ -0,0 +1,66 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_LOG_LOGGER_H +#define FLATCFG_LOG_LOGGER_H + +#include "common.h" +#include "log_stream.h" + +#include + +#include +#include + +namespace flatcfg +{ +namespace log +{ + +/// @brief Class which will create loggers of various levels. +/// @details Any stream from a function whose log level is lower than the log +/// level the logger instance was constructed with will behave as if +/// the log level is kOff. +class Logger +{ +public: + /// @brief Construct logger from a name and log level. + Logger(std::string_view name, LogLevel logLevel) noexcept; + + /// @brief Create log stream which logs at the fatal level. + LogStream LogFatal() noexcept; + + /// @brief Create log stream which logs at the error level. + LogStream LogError() noexcept; + + /// @brief Create log stream which logs at the warn level. + LogStream LogWarn() noexcept; + + /// @brief Create log stream which logs at the info level. + LogStream LogInfo() noexcept; + + /// @brief Create log stream which logs at the debug level. + LogStream LogDebug() noexcept; + + /// @brief Create log stream which logs at the verbose level. + LogStream LogVerbose() noexcept; + +private: + /// @brief Create log stream which logs at the given level. + LogStream LogAt(LogLevel logLevel) noexcept; + + /// @brief Level beneath which we will not log. + LogLevel m_logLevel {}; + + /// @brief Name of instance, not null-terminated. + std::array m_nameBuf {}; + + /// @brief Size of name. + std::size_t m_nameSize {}; +}; + +} // namespace log +} // namespace flatcfg + +#endif // FLATCFG_LOG_LOGGER_H diff --git a/src/flatcfg/src/flatcfg/utils/ctc.h b/src/flatcfg/src/flatcfg/utils/ctc.h new file mode 100644 index 0000000..7be9e8d --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/ctc.h @@ -0,0 +1,26 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_CTC_H +#define FLATCFG_UTILS_CTC_H + +#ifdef __CTC__ + #define FLATCFG_SKIPCOV_BEGIN() \ + _Pragma("CTC SKIP") \ + static_assert(true, "") +#else + #define FLATCFG_SKIPCOV_BEGIN() \ + static_assert(true, "") +#endif + +#ifdef __CTC__ + #define FLATCFG_SKIPCOV_END() \ + _Pragma("CTC ENDSKIP") \ + static_assert(true, "") +#else + #define FLATCFG_SKIPCOV_END() \ + static_assert(true, "") +#endif + +#endif // FLATCFG_UTILS_CTC_H diff --git a/src/flatcfg/src/flatcfg/utils/environment.cpp b/src/flatcfg/src/flatcfg/utils/environment.cpp new file mode 100644 index 0000000..581666b --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/environment.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "environment.h" + +#include +#include +#include +#include +#include + +using score::cpp::span; + +namespace flatcfg +{ +namespace utils +{ + +std::optional +Environment::get(std::string_view envName, span outputBuffer) noexcept +{ + // unsupported buffer size -> nullopt + constexpr unsigned long long umax_ptrdiff = static_cast(std::numeric_limits::max()); + if (outputBuffer.empty() || outputBuffer.size() > umax_ptrdiff) + { + return std::nullopt; + } + + // nullptr -> nullopt + char *envPtr = std::getenv(envName.data()); + if (envPtr == nullptr) + { + return std::nullopt; + } + + // copy environment variable into buffer + static_cast(std::strncpy(outputBuffer.data(), envPtr, outputBuffer.size())); + + // find null-terminator + auto it = std::find(outputBuffer.begin(), outputBuffer.end(), '\0'); + if (it == outputBuffer.end()) + { + // no null-terminator written, environment variable value was too large + outputBuffer.back() = '\0'; + return -static_cast(outputBuffer.size() - 1U); + } + else + { + return std::distance(outputBuffer.begin(), it); + } +} + +std::optional +Environment::processIdentifier(span outputBuffer) noexcept +{ + return get("PROCESSIDENTIFIER", outputBuffer); +} + +std::optional +Environment::ecuCfgRootFolder(span outputBuffer) noexcept +{ + return get("ECUCFG_ENV_VAR_ROOTFOLDER", outputBuffer); +} + +} // namespace utils +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/utils/environment.h b/src/flatcfg/src/flatcfg/utils/environment.h new file mode 100644 index 0000000..ccf6ab9 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/environment.h @@ -0,0 +1,51 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_ENVIRONMENT_H +#define FLATCFG_UTILS_ENVIRONMENT_H + +#include "score/span.hpp" + +#include +#include +#include + +namespace flatcfg +{ +namespace utils +{ + +/// @brief Wrapper class to make it clear that functions are not member +/// functions of whichever other class they're called in. +class Environment +{ +public: + /// @brief Reads an environment variable's contents into a span. + /// @details If the environment variable is not set, or the size of the + /// output buffer exceeds PTRDIFF_MAX or is 0, nullopt is returned. + /// Otherwise, the contents of the environment variable is copied until + /// the span is filled or the null-terminator is read. In both cases, the + /// last character written is a null-terminator. + /// If the span was filled before reading a null-terminator, the size of + /// the string written to the span (not including the null-terminator) is + /// returned as a negative value, otherwise the positive value is returned. + /// @warning The envName MUST be null-terminated. + /// @pre The output buffer span does not point to the environment variable value. + /// @post Any remaining bytes after the null-terminator in the output buffer are zeroed. + static std::optional + get(std::string_view envName, score::cpp::span outputBuffer) noexcept; + + /// @brief Calls get(...) with PROCESSIDENTIFIER as the environment variable. + static std::optional + processIdentifier(score::cpp::span outputBuffer) noexcept; + + /// @brief Calls get(...) with ECUCFG_ENV_VAR_ROOTFOLDER as the environment variable. + static std::optional + ecuCfgRootFolder(score::cpp::span outputBuffer) noexcept; +}; + +} // namespace utils +} // namespace flatcfg + +#endif // FLATCFG_UTILS_ENVIRONMENT_H diff --git a/src/flatcfg/src/flatcfg/utils/fb_verifier.cpp b/src/flatcfg/src/flatcfg/utils/fb_verifier.cpp new file mode 100644 index 0000000..4243223 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/fb_verifier.cpp @@ -0,0 +1,113 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/result/result.h" + +#include "flatbuffers/flatbuffers.h" +#include "flatcfg/flatcfg_error.h" +#include "ctc.h" +#include "fb_verifier.h" + +FLATCFG_SKIPCOV_BEGIN(); + +namespace { + +/// @brief Helper class to check for fields required by SCORE in binary. +class RequiredFields final : public flatbuffers::Table { +public: + /// @brief Verify that we can parse required fields. + /// @note Required and called by flatbuffers::Verifier::VerifyBuffer(...). + bool Verify(flatbuffers::Verifier& verifier) const { + return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_FUNCTION_CLUSTER) && verifier.EndTable(); + } + + /// @brief Convert offset from .fbs file to VTable offset. + static constexpr flatbuffers::voffset_t schemaIdToVtableOffset(int id) noexcept { + // helper typedef + using type = flatbuffers::voffset_t; + + // coverity[autosar_cpp14_a4_7_1_violation:Intentional] Because the implementation is designed not to overflow. + type offset = static_cast(static_cast(static_cast(id) + static_cast(2)) * sizeof(type)); + return offset; + } + + // vtable offset for each required field in the table + // use variables rather than enum so that we don't need to cast + static const flatbuffers::voffset_t VT_FUNCTION_CLUSTER; + static const flatbuffers::voffset_t VT_VERSION_MAJOR; + static const flatbuffers::voffset_t VT_VERSION_MINOR; +}; + +// out of line definition because inline variables are a C++17 feature +constexpr flatbuffers::voffset_t RequiredFields::VT_FUNCTION_CLUSTER{schemaIdToVtableOffset(0)}; +constexpr flatbuffers::voffset_t RequiredFields::VT_VERSION_MAJOR{schemaIdToVtableOffset(1)}; +constexpr flatbuffers::voffset_t RequiredFields::VT_VERSION_MINOR{schemaIdToVtableOffset(2)}; + +} // namespace + +namespace flatcfg { +namespace utils { + +score::ResultBlank +// RULECHECKER_comment(1, 1, check_max_parameters, "Four parameters is reasonable here.", true); +FbVerifier::verifyBinary(std::string_view binaryData, std::string_view functionCluster, std::int32_t versionMajor, + std::int32_t versionMinor) noexcept { + // helper typedef + using T = RequiredFields; + + // verify we can parse buffer and its fields + flatbuffers::Verifier verifier{// RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "Input is std::string_view + // (char*) but function needs unsigned char*.", true) + reinterpret_cast(binaryData.data()), binaryData.size()}; + if (!verifier.VerifyBuffer()) { + return score::MakeUnexpected(FlatCfgErrorCode::kInvalidBinaryFormat); + } + + // now we can get root of buffer + const T* self{flatbuffers::GetRoot(binaryData.data())}; + + // every type has a default value (string is "", integers are 0, etc.) + // if a field has the default value, it's (usually) not written to the binary file + // so we must interpret absence of a field as if the field had the default value when written + + // verify functionCluster field is present and has the correct value + const flatbuffers::String* fcName{self->GetPointer(T::VT_FUNCTION_CLUSTER)}; + if ((fcName == nullptr && !functionCluster.empty()) || + (fcName != nullptr && fcName->size() != functionCluster.size())) { + return score::MakeUnexpected(FlatCfgErrorCode::kBadName); + } + + for (std::size_t i = 0U; i < functionCluster.size(); ++i) { + // fcName cannot be null here because we checked size + const flatbuffers::String& fc = *fcName; + flatbuffers::uoffset_t index = static_cast(i); + // RULECHECKER_comment(1, 1, check_underlying_signedness_conversion, "There is no conversion here that could + // result in the wrong value.", false) + auto lhs = std::tolower(static_cast(fc[index])); + auto rhs = functionCluster[i]; + if (lhs != rhs) { + return score::MakeUnexpected(FlatCfgErrorCode::kBadName); + } + } + + // verify versionMajor field is present and has the correct value + int32_t vMajor{self->GetField(T::VT_VERSION_MAJOR, 0)}; + if (vMajor != versionMajor) { + return score::MakeUnexpected(FlatCfgErrorCode::kBadVersionMajor); + } + + // verify versionMinor field is present and has the correct value + int32_t vMinor{self->GetField(T::VT_VERSION_MINOR, 0)}; + if (vMinor != versionMinor) { + return score::MakeUnexpected(FlatCfgErrorCode::kBadVersionMinor); + } + + // success + return score::ResultBlank(); +} + +} // namespace utils +} // namespace flatcfg + +FLATCFG_SKIPCOV_END(); diff --git a/src/flatcfg/src/flatcfg/utils/fb_verifier.h b/src/flatcfg/src/flatcfg/utils/fb_verifier.h new file mode 100644 index 0000000..40d47f6 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/fb_verifier.h @@ -0,0 +1,32 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_FB_VERIFIER_H +#define FLATCFG_UTILS_FB_VERIFIER_H + +#include "score/result/result.h" + +#include +#include + +namespace flatcfg +{ +namespace utils +{ + +/// @brief Wrapper class around flatbuffers verification helpers. +class FbVerifier +{ +public: + /// @brief Verify a flatbuffers binary's function cluster info. + static score::ResultBlank + verifyBinary(std::string_view binaryData, + std::string_view functionCluster, + std::int32_t versionMajor, std::int32_t versionMinor) noexcept; +}; + +} // namespace utils +} // namespace flatcfg + +#endif // FLATCFG_UTILS_FB_VERIFIER_H diff --git a/src/flatcfg/src/flatcfg/utils/libc.cpp b/src/flatcfg/src/flatcfg/utils/libc.cpp new file mode 100644 index 0000000..7e0c5d2 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/libc.cpp @@ -0,0 +1,355 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "libc.h" +#include "ctc.h" +#include "logging.h" +#include "flatcfg/flatcfg_error.h" + +#include "score/result/result.h" + +#include +#include +#include + +using score::cpp::span; +using score::Result; + +#define ErrnoResV(var) \ + score::MakeUnexpected(static_cast(errno), "Set errno after returning = " + std::to_string(errno)) + +#define ErrnoResT(type) \ + score::MakeUnexpected(static_cast(errno), "Set errno after returning = " + std::to_string(errno)) + +FLATCFG_SKIPCOV_BEGIN(); + +namespace flatcfg +{ +namespace utils +{ + +#if FLATCFG_INTERNAL_HAS_POSIX + +pid_t +Libc::getpid() noexcept +{ + return ::getpid(); +} + +Result +Libc::fileno(std::FILE *stream) noexcept +{ + int fd {}; + do { + fd = ::fileno(stream); + } + while (fd < 0 && errno == EINTR); + if (fd < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno; + return ErrnoResV(fd); + } + return fd; +} + +Result +Libc::stat(const char *path) noexcept +{ + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct ::stat st {}; + int res {}; + do { + res = ::stat(path, &st); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " for path: " << path; + return ErrnoResV(st); + } + return st; +} + +Result +Libc::lstat(const char *path) noexcept +{ + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct ::stat st {}; + int res {}; + do { + res = ::lstat(path, &st); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " for path: " << path; + return ErrnoResV(st); + } + return st; +} + +Result +Libc::fstat(int fd) noexcept +{ + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct ::stat st {}; + int res {}; + do { + res = ::fstat(fd, &st); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " for file descriptor " << fd; + return ErrnoResV(st); + } + return st; +} + +Result +Libc::fstatat(int dirFd, const char *path, int flags) noexcept +{ + // RULECHECKER_comment(1, 1, check_union_object, "Union is part of struct ::stat, nothing we can do.", true) + struct ::stat st {}; + int res {}; + do { + res = ::fstatat(dirFd, path, &st, flags); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " for file descriptor " << dirFd << + ", flags " << flags << ", and path: " << path; + return ErrnoResV(st); + } + return st; +} + +Result +Libc::readlink(const char *path, span buf) noexcept +{ + ssize_t ssize {}; + do { + ssize = ::readlink(path, buf.data(), buf.size()); + } + while (ssize < 0 && errno == EINTR); + if (ssize < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " with buffer size " << buf.size() << + " for path: " << path; + return ErrnoResT(std::size_t); + } + if (ssize == 0) + { + FLATCFG_LOG_WARN() << + "read an empty link from path: " << path; + } + return static_cast(ssize); +} + +Result +Libc::open(const char *path, int flags) noexcept +{ + int fd {}; + do { + fd = ::open(path, flags); + } + while (fd < 0 && errno == EINTR); + if (fd < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " for flags " << flags << " and" + " path: " << path; + return ErrnoResV(fd); + } + return fd; +} + +void +Libc::close(int fd) noexcept +{ + while (::close(fd) < 0 && errno == EINTR); +} + +Result +Libc::read(int fd, void *buf, const std::size_t count) noexcept +{ + // skip everything if count == 0 so we don't get stuck in a loop + if (count == 0U) + { + FLATCFG_LOG_DEBUG() << + "count is 0; skipping"; + return count; + } + + // actual implementation + // RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "Need to iterate but ::read takes void *.", true) + auto *uc_buf = reinterpret_cast(buf); + std::size_t bytesRead {}; + ssize_t res {}; + do { + res = ::read(fd, buf, count - bytesRead); + if (res > 0) + { + std::advance(uc_buf, res); + bytesRead += static_cast(res); + } + } + while ((bytesRead != count && res != 0) || (res < 0 && errno == EINTR)); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " with buffer size " << count << + " for file descriptor " << fd; + return ErrnoResV(bytesRead); + } + FLATCFG_LOG_VERBOSE() << + "read " << bytesRead << " bytes (requested count was " << count << + " ) from file descriptor " << fd; + return bytesRead; +} + +Result +// RULECHECKER_comment(1, 1, check_max_parameters, "Parameters required by ::write.", true) +Libc::write(int fd, const void *buf, std::size_t count) noexcept +{ + // skip everything if count == 0 so we don't get stuck in a loop + if (count == 0U) + { + return 0; // == count + } + + // actual implementation + // RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "Need to iterate but ::write takes void *.", true) + auto *uc_buf = reinterpret_cast(buf); + std::size_t bytesWritten {}; + ssize_t res {}; + do { + res = ::write(fd, buf, count - bytesWritten); + if (res > 0) + { + std::advance(uc_buf, res); + bytesWritten += static_cast(res); + } + } + while ((bytesWritten != count) || (res < 0 && errno == EINTR)); + if (res < 0) + { + return ErrnoResV(bytesWritten); + } + return bytesWritten; +} + +#endif + +#if FLATCFG_INTERNAL_HAS_LINUX + +Result +Libc::sys_getdents64(int fd, void *buf, std::size_t size) noexcept +{ + long res {}; + do { + res = ::syscall(SYS_getdents64, fd, buf, size); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " with buffer size " << size << + " for file descriptor " << fd; + return ErrnoResT(std::size_t); + } + FLATCFG_LOG_VERBOSE() << + "read " << res << " bytes (buffer size was " << size << ") from file" + " descriptor " << fd; + return static_cast(res); +} + +#endif + +#if FLATCFG_INTERNAL_HAS_QNX + +Result +// RULECHECKER_comment(1, 1, check_max_parameters, "Parameters required by ::_readx.", true) +Libc::_readx(int fd, void *buf, std::size_t size, unsigned xtype, void *xdata, + std::size_t xdataSize) noexcept +{ + ssize_t res {}; + do { + res = ::_readx(fd, buf, size, xtype, xdata, xdataSize); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " with buffer size " << size << + " for file descriptor " << fd; + return ErrnoResT(std::size_t); + } + FLATCFG_LOG_VERBOSE() << + "read " << res << " bytes (buffer size was " << size << ") from file" + " descriptor " << fd; + return static_cast(res); +} + +#endif + +#if FLATCFG_INTERNAL_HAS_MACOS + +Result +Libc::sys_getdirentries64(int fd, void *buf, std::size_t size, long *basep) noexcept +{ + long res {}; + do { + res = ::syscall(SYS_getdirentries64, fd, buf, size, basep); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " with buffer size " << size << + " for file descriptor " << fd; + return ErrnoResT(std::size_t); + } + FLATCFG_LOG_VERBOSE() << + "read " << res << " bytes (buffer size was " << size << ") from file" + " descriptor " << fd; + return static_cast(res); +} + +Result +Libc::proc_pidpath(int pid, void *buf, std::uint32_t size) noexcept +{ + int res {}; + do { + res = ::proc_pidpath(pid, buf, size); + } + while (res < 0 && errno == EINTR); + if (res < 0) + { + FLATCFG_LOG_DEBUG() << + "failed with errno " << errno << " with buffer size " << size << + " for pid " << pid; + return ErrnoResT(std::size_t); + } + if (res == 0) + { + FLATCFG_LOG_WARN() << + "read an empty process path for the pid " << pid; + } + return static_cast(res); +} + +#endif + +} // namespace utils +} // namespace flatcfg + +FLATCFG_SKIPCOV_END(); diff --git a/src/flatcfg/src/flatcfg/utils/libc.h b/src/flatcfg/src/flatcfg/utils/libc.h new file mode 100644 index 0000000..b7c2a87 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/libc.h @@ -0,0 +1,163 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_LIBC_H +#define FLATCFG_UTILS_LIBC_H + +#include "flatcfg/fs/_os.h" + +#include "score/span.hpp" +#include "score/result/result.h" + +#if FLATCFG_INTERNAL_HAS_POSIX + #include + #include + #include + #include + #include + #include +#endif + +#if FLATCFG_INTERNAL_HAS_LINUX + #include +#endif + +#if FLATCFG_INTERNAL_HAS_QNX + #include +#endif + +#if FLATCFG_INTERNAL_HAS_MACOS + #include + #include +#endif + +namespace flatcfg +{ +namespace utils +{ + +/// @brief Wrapper around all libc functions which we call. +/// @note This is not to enable mocking; we can already override all public +/// libc functions because they are weakly linked. +/// This is so that we can change these functions' behaviour without +/// affecting our test framework. +/// @details All wrappers retry infinitely on EINTR. If the libc function +/// sets errno to any other value, a Result with an ErrorCode of +/// kOsError is returned, with support data being set to errno. +/// Errno is NOT cleared by these wrappers. +class Libc +{ +public: +#if FLATCFG_INTERNAL_HAS_POSIX + /// @brief Get the process ID of the calling process. + /// @headerfile + /// @note Cannot fail. + static pid_t + getpid() noexcept; + + /// @brief Obtain file descriptor of a stdio stream. + /// @headerfile + static score::Result + fileno(std::FILE *stream) noexcept; + + /// @brief Get statistics of the file pointed to by the path. + /// @warning Path must be null-terminated. + /// @headerfile + static score::Result + stat(const char *path) noexcept; + + /// @brief Get statistics of the file pointed to by the path, not following + /// symbolic links. + /// @warning Path must be null-terminated. + /// @headerfile + static score::Result + lstat(const char *path) noexcept; + + /// @brief Get statistics of the file pointed to by the file descriptor. + /// @headerfile + static score::Result + fstat(int fd) noexcept; + + /// @brief Get statistics of the file named by the name in the directory + /// pointed to by the file descriptor. + /// @warning Path must be null-terminated. + /// @headerfile + static score::Result + fstatat(int dirFd, const char *path, int flags) noexcept; + + /// @brief Get statistics of a file relative to a directory file descriptor. + /// @headerfile + static score::Result + fstatvfs(int fd) noexcept; + + /// @brief Read the contents of a symbolic link. + /// @warning Path must be null-terminated. + /// @warning Does not append a terminating null byte. + /// @headerfile + static score::Result + readlink(const char *path, score::cpp::span buf) noexcept; + + /// @brief Open and possibly create a file. + /// @warning Path must be null-terminated. + /// @headerfile + static score::Result + open(const char *path, int flags) noexcept; + + /// @brief Close a file descriptor. + /// @headerfile + static void + close(int fd) noexcept; + + /// @brief Read bytes from a file descriptor. + /// @headerfile + /// @note Will loop internally until count bytes are read, eof is reached, + /// or an error occurs. + static score::Result + read(int fd, void *buf, std::size_t count) noexcept; + + /// @brief Write bytes to a file descriptor. + /// @headerfile + /// @note Will loop internally until count bytes are written or an error + /// occurs. + /// @warning Special care needs to be taken with logging in write so we + /// don't end up with infinite recursion. + static score::Result + write(int fd, const void *buf, std::size_t count) noexcept; +#endif + +#if FLATCFG_INTERNAL_HAS_LINUX + /// @brief Read whole linux_dirent structs into a buffer whose size must be + /// at least the filesystem block size. + /// @headerfile + static score::Result + sys_getdents64(int fd, void *buf, std::size_t size) noexcept; +#endif + +#if FLATCFG_INTERNAL_HAS_QNX + /// @brief Reads whole dirent structs into a buffer whose size must be at + /// least the filesystem block size. + /// @headerfile + static score::Result + _readx(int fd, void *buf, std::size_t size, + unsigned xtype, void *xdata, std::size_t xdataSize) noexcept; +#endif + +#if FLATCFG_INTERNAL_HAS_MACOS + /// @brief Reads whole macos_dirent structs into a buffer whose size must be + /// at least the filesystem block size. + /// @headerfile + static score::Result + sys_getdirentries64(int fd, void *buf, std::size_t size, long *basep) noexcept; + + /// @brief Reads the path to the executable of the process into a buffer. + /// @warning The buffer size must be exactly PROC_PIDPATHINFO_MAXSIZE. + static score::Result + proc_pidpath(int pid, void *buf, std::uint32_t size) noexcept; +#endif +}; + +} // namespace utils +} // namespace flatcfg + +#endif // FLATCFG_UTILS_LIBC_H diff --git a/src/flatcfg/src/flatcfg/utils/logging.h b/src/flatcfg/src/flatcfg/utils/logging.h new file mode 100644 index 0000000..84fd5ed --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/logging.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_LOGGING_H +#define FLATCFG_UTILS_LOGGING_H + +#include "pretty_function.h" + +#include "flatcfg/log/logger.h" + +namespace flatcfg +{ +namespace utils +{ +namespace _impl +{ + +/// @brief Get static logger instance. +/// @note Inline so that we don't need to mock it in unit tests. +inline log::Logger& +getLogger() noexcept +{ + // RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "Static variable is a function local so will not cause issues.", true) + static log::Logger logger { "flatcfg", log::LogLevel::kInfo }; + return logger; +} + +} // namespace _impl +} // namespace utils +} // namespace flatcfg + +#define FLATCFG_LOG_(level) \ + (flatcfg::utils::_impl::getLogger().Log##level() \ + << '[' << FLATCFG_PRETTY_FUNCTION << "] ") + +#define FLATCFG_LOG_FATAL() FLATCFG_LOG_(Fatal) +#define FLATCFG_LOG_ERROR() FLATCFG_LOG_(Error) +#define FLATCFG_LOG_WARN() FLATCFG_LOG_(Warn) +#define FLATCFG_LOG_INFO() FLATCFG_LOG_(Info) +#define FLATCFG_LOG_DEBUG() FLATCFG_LOG_(Debug) +#define FLATCFG_LOG_VERBOSE() FLATCFG_LOG_(Verbose) + +#endif // FLATCFG_UTILS_LOGGING_H diff --git a/src/flatcfg/src/flatcfg/utils/path_components.cpp b/src/flatcfg/src/flatcfg/utils/path_components.cpp new file mode 100644 index 0000000..bc193fa --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/path_components.cpp @@ -0,0 +1,181 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "path_components.h" +#include "logging.h" + +#include "flatcfg/flatcfg_error.h" + +#include + +using score::Result; + +namespace +{ + +constexpr std::string_view PROC_NAME_CHARS { "abcdefghijklmnopqrstuvwxyz0123456789_", 37 }; +// coverity[autosar_cpp14_m3_4_1_violation:Intentional] Defined here for better maintainability. +constexpr std::string_view FC_NAME_CHARS { PROC_NAME_CHARS.substr(0U, 26U) }; +// coverity[autosar_cpp14_m3_4_1_violation:Intentional] Defined here for better maintainability. +constexpr std::string_view FLATCFG_BIN_SUFFIX { "_flatcfg.bin", 12U }; + +} // namespace + +namespace flatcfg +{ +namespace utils +{ + +Result +PathComponents::FromPath(std::string_view path) noexcept +{ + // ensure path is not empty + if (path.empty()) + { + FLATCFG_LOG_WARN() << + "path cannot be empty"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + } + + // ensure the file name is not empty + if (path.back() == '/') + { + FLATCFG_LOG_WARN() << + "the final name component cannot be empty in the path: " << path; + return score::MakeUnexpected(FlatCfgErrorCode::kBadPathName); + } + + PathComponents comps; + comps.filePath = path; + + // path should still contain: (.*/)? [a-z]+ (_[a-z0-9](_?[a-z0-9]+)*)? __.+ _flatcfg\.bin + // parse file name and save to path + { + auto lastSlashPos = path.find_last_of('/'); + if (lastSlashPos == std::string_view::npos) + { + comps.fileName = path; + } + else + { + // skip last slash + ++lastSlashPos; + // empty file name check done earlier + comps.fileName = path.substr(lastSlashPos); + } + path = comps.fileName; + } + + // path should still contain: [a-z]+ (_[a-z0-9](_?[a-z0-9]+)*)? __.+ _flatcfg\.bin + // remove: _flatcfg\.bin + { + // coverity[autosar_cpp14_a4_7_1_violation:Intentional] Will not wrap due to program design. + if (path.rfind(FLATCFG_BIN_SUFFIX) != (path.size() - FLATCFG_BIN_SUFFIX.size())) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; name does not end in" + " '_flatcfg.bin': " << comps.filePath; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + path.remove_suffix(FLATCFG_BIN_SUFFIX.size()); + } + + // path should still contain: [a-z]+ (_[a-z0-9](_?[a-z0-9]+)*)? __.+ + // parse function cluster and remove: [a-z]+ + { + auto firstUnderscorePos = path.find_first_of('_'); + if (firstUnderscorePos == std::string_view::npos) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; name does not contain" + " a '_': " << comps.filePath; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + else if (firstUnderscorePos == 0U) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; name begins with a" + " '_' (no function cluster name): " << comps.filePath; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + else + { + comps.functionCluster = path.substr(0U, firstUnderscorePos); + for (char c : comps.functionCluster) + { + if (FC_NAME_CHARS.find(c) == std::string_view::npos) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; function" + " cluster component does not match '[a-z]+': " << + comps.filePath; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + } + path.remove_prefix(comps.functionCluster.size()); + } + } + + // path should still contain: (_[a-z0-9](_?[a-z0-9]+)*)? __.+ + // parse process and remove: (_[a-z0-9](_?[a-z0-9]+)*)? + { + auto firstDoubleUnderscorePos = path.find("__"); + if (firstDoubleUnderscorePos == std::string_view::npos) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; name does not contain" + " a '__': " << comps.fileName; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + else if (firstDoubleUnderscorePos == 0U) + { + comps.process = std::nullopt; + } + else + { + // skip leading underscore + path.remove_prefix(1U); + --firstDoubleUnderscorePos; + comps.process = path.substr(0U, firstDoubleUnderscorePos); + for (char c : *comps.process) + { + if (PROC_NAME_CHARS.find(c) == std::string_view::npos) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; process name" + " component does not match '(_[a-z0-9](_?[a-z0-9]+)*)?__': " << + comps.filePath; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + } + path.remove_prefix(comps.process->size()); + } + } + + // path should still contain: __.+ + // parse software cluster and remove: __.+ + { + // remove leading double underscore + path.remove_prefix(2U); + if (path.empty()) + { + FLATCFG_LOG_DEBUG() << + "path does not reference a configuration; does not contain a" + " software cluster name: " << comps.filePath; + return score::MakeUnexpected(FlatCfgErrorCode::kBadArgument); + } + comps.softwareCluster = path; + } + + // success + FLATCFG_LOG_DEBUG() << + "successfully parsed components (functionClusterName=" << + comps.functionCluster << ", softwareClusterName=" << comps.softwareCluster << + ", processName=" << comps.process.value_or("{none}") << ") from" + " path: " << comps.filePath; + return comps; +} + +} // namespace utils +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/utils/path_components.h b/src/flatcfg/src/flatcfg/utils/path_components.h new file mode 100644 index 0000000..10e4d18 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/path_components.h @@ -0,0 +1,43 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_PATH_COMPONENTS_H +#define FLATCFG_UTILS_PATH_COMPONENTS_H + +#include "score/result/result.h" + +#include +#include + +namespace flatcfg +{ +namespace utils +{ + +/// @brief Helper type to hold views to components of a config file path. +struct PathComponents +{ + std::string_view filePath; + std::string_view fileName; + std::string_view functionCluster; + std::string_view softwareCluster; + std::optional process; + + /// @brief Parse a config file path into its components. + /// @return The components, or an error if the path was not formatted correctly. + /// @pre Path matches the regex: + /// (.*/)? parent dirs + /// [a-z]+ function cluster + /// (_[a-z0-9](_?[a-z0-9]+)*)? process (leading underscore) + /// __.+ software cluster (leading double underscore) + /// _flatcfg\.bin suffix + /// @post All std::string_views will be non-empty and substrings of input path. + static score::Result + FromPath(std::string_view path) noexcept; +}; + +} // namespace utils +} // namespace flatcfg + +#endif // FLATCFG_UTILS_PATH_COMPONENTS_H diff --git a/src/flatcfg/src/flatcfg/utils/pretty_function.h b/src/flatcfg/src/flatcfg/utils/pretty_function.h new file mode 100644 index 0000000..7067506 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/pretty_function.h @@ -0,0 +1,324 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_PRETTY_FUNCTION_H +#define FLATCFG_UTILS_PRETTY_FUNCTION_H + +#include +#include +#include + +/// @brief Custom __PRETTY_FUNCTION__ without parameters, return type, or namespaces. +#define FLATCFG_PRETTY_FUNCTION flatcfg::utils::PrettyFunction::Create(__PRETTY_FUNCTION__) + +namespace flatcfg +{ +namespace utils +{ + +/// @brief Functions to implement custom __PRETTY_FUNCTION__ without parameters, return type, or namespaces. +/// @note This is intentionally header only since there's no point in mocking pure functions. +class PrettyFunction +{ +public: + /// @brief Call all functions in correct order, and return our own version of __PRETTY_FUNCTION__. + static inline constexpr std::string_view + Create(std::string_view sv) noexcept + { + return _trim(_removeNamespaces(_removeReturnType(_removeFunctionParams(_removeTemplateTypesList(sv))))); + } + + /// @brief Removes preceding and trailing space characters. + static inline constexpr std::string_view + // coverity[autosar_cpp14_a15_5_2_violation] The design of the program does not raise the std::terminate exception. + // coverity[autosar_cpp14_a15_5_3_violation] The design of the program does not raise the std::terminate exception. + // coverity[exn_spec_violation] No try-catch is required because the design of the program does not raise exceptions. + _trim(std::string_view sv) noexcept + { + // find preceding spaces + std::size_t posPre { 0U }; + while ((posPre < sv.size()) && (sv[posPre] == ' ')) + { + ++posPre; + } + + // find trailing spaces + std::size_t posPost { sv.size() - 1U }; + while ((posPost >= 1U) && (sv[posPost] == ' ')) + { + --posPost; + } + + // remove spaces + // coverity[autosar_cpp14_a15_4_2_violation] The design of the program does not raise the std::out_of_range exception. + return sv.substr(0U, posPost + 1U).substr(std::min(posPre, sv.size())); + } + + /// @brief Removes template types list, if it exists, from a string obtained from __PRETTY_FUNCTION__. + /// example:'T::T() [with U = int; X = std::vector]' -> 'T::T()'. + /// @pre Input must be obtained directly from __PRETTY_FUNCTION__. + static inline constexpr std::string_view + // coverity[autosar_cpp14_a15_5_2_violation] The design of the program does not raise the std::terminate exception. + // coverity[autosar_cpp14_a15_5_3_violation] The design of the program does not raise the std::terminate exception. + // coverity[exn_spec_violation] No try-catch is required because the design of the program does not raise exceptions. + _removeTemplateTypesList(std::string_view sv) noexcept + { + // remove template types: [with U = int; X = std::vector] + std::size_t pos { 0U }; + for (; pos < sv.size(); ++pos) + { + // find opening of [] + if (sv[pos] == '[') + { + // check that we're not on the first character (shouldn't be possible if pre-conditions are followed) + if (pos == 0U) + { + // return directly with no modifications + return sv; + } + + // check that we've found template type list and not operator[] + // template type list will have a space preceding it + // e.g. T::T() [with U = int; X = std::vector] + // -------------^------------------------------ + else if (sv[pos - 1U] == ' ') + { + // put pos on space before [] + --pos; + break; + } + + // found operator[]; skip + // e.g. T::operator[]() + // -----------------^--- + else + { + continue; + } + } + } + + // position is currently on opening bracket + // e.g. T::T() [with U = int; X = std::vector] + // -------------^------------------------------ + + // return modified view + // coverity[autosar_cpp14_a15_4_2_violation] The design of the program does not raise the std::out_of_range exception. + return _trim(sv.substr(0U, pos)); + } + + /// @brief Removes function params, if they exist, from a string obtained from _removeTemplateTypesList(...). + /// example: 'operator[](int (*)())' -> 'operator[]'. + /// @pre Input must be obtained directly from _removeTemplateTypesList(...). + static inline constexpr std::string_view + // coverity[autosar_cpp14_a15_5_2_violation] The design of the program does not raise the std::terminate exception. + // coverity[autosar_cpp14_a15_5_3_violation] The design of the program does not raise the std::terminate exception. + // coverity[exn_spec_violation] No try-catch is required because the design of the program does not raise exceptions. + _removeFunctionParams(std::string_view sv) noexcept + { + // remove all characters from end until first closing brace + // potentially the function is const, volatile, or reference qualified + // e.g. int T::foo() const + // -----------------^ + while (!sv.empty() && sv.back() != ')') + { + sv = sv.substr(0U, sv.size() - 1U); + } + + // check that input is not empty + if (sv.empty()) + { + return sv; + } + + // position is currently on outer closing brace + // e.g. (int (*)()) + // ----------^ + std::size_t pos { sv.size() - 1U }; + + // keep track of nested () + std::ptrdiff_t stackSize { 1 }; + + // remove function params: (int (*)()) + // do '>= 1U' to avoid unsigned overflow issues + for (; (stackSize > 0) && (pos >= 1U); --pos) + { + // found new nested closing brace + // e.g. (int (*)()) + // ---------^- + if (sv[pos - 1U] == ')') + { + ++stackSize; + } + + // found new nested opening brace + // e.g. (int (*)()) + // --------^-- + else if (sv[pos - 1U] == '(') + { + --stackSize; + } + + // found normal character; skip + else + { + continue; + } + } + + // position is currently on outer opening brace + // e.g. (int (*)()) + // ^---------- + + // return modified view + // coverity[autosar_cpp14_a15_4_2_violation] The design of the program does not raise the std::out_of_range exception. + return _trim(sv.substr(0U, pos)); + } + + /// @brief Removes function return types from a string obtained from _removeFunctionParams(...). + /// example: 'void ns::detail::T::operator[]' -> 'ns::detail::T::operator[]'. + /// @pre Input must be obtained directly from _removeFunctionParams(...). + static inline constexpr std::string_view + // RULECHECKER_comment(4, 1, check_max_control_nesting_depth, "Nested conditionals formatted clearly, make understanding intention easier for reviewers.", true); + // coverity[autosar_cpp14_a15_5_2_violation] The design of the program does not raise the std::terminate exception. + // coverity[autosar_cpp14_a15_5_3_violation] The design of the program does not raise the std::terminate exception. + // coverity[exn_spec_violation] No try-catch is required because the design of the program does not raise exceptions. + _removeReturnType(std::string_view sv) noexcept + { + // check that input end is not on a space + if (sv.empty() || sv.back() == ' ') + { + return sv; + } + + // position is currently on end of function name + // e.g. void ns::outer::inner::fn OR int main + // ------------------------------------^ -------^ + std::size_t pos { sv.size() - 1U }; + + // keep track of nested <> + std::ptrdiff_t stackSize { 0 }; + + // go to start of function name, including enclosing classes and namespaces + // do this by finding first space where stackSize == 0 + // do '>= 1U' to avoid unsigned overflow issues + for (; pos >= 1U; --pos) + { + // found new nested closing angle bracket + // e.g. void ns::outer::inner::fn + // --------------------------------^---- + if (sv[pos - 1U] == '>') + { + // coverity[autosar_cpp14_a4_7_1_violation] it is used only for constexpr functions and for processing debug log results + ++stackSize; + } + + // found new nested opening angle bracket + // e.g. void ns::outer::inner::fn + // ---------------------------^--------- + else if (sv[pos - 1U] == '<') + { + // coverity[autosar_cpp14_a4_7_1_violation] it is used only for constexpr functions and for processing debug log results + --stackSize; + } + + // found a space + else if (sv[pos - 1U] == ' ') + { + // stack size is 0 + // e.g. void ns::outer::inner::fn + // ----^-------------------------------- + if (stackSize == 0) + { + break; + } + + // stack size is not 0 + // e.g. void ns::outer::inner::fn + // ------------------------------^------ + else + { + continue; + } + } + + // found a normal character; skip + else + { + continue; + } + } + + // position is currently at start of function name, including enclosing classes and namespaces + // e.g. void ns::outer::inner::fn OR int main + // -----^------------------------------- ----^--- + + // return modified view + // coverity[autosar_cpp14_a15_4_2_violation] The design of the program does not raise the std::out_of_range exception. + return _trim(sv.substr(pos)); + } + + /// @brief Removes function namespaces from a string obtained from _removeReturnType(...). + /// example: 'ns::detail::T::operator[]' -> 'T::operator[]'. + /// @pre Input must be obtained directly from _removeReturnType(...). + /// @pre All namespaces must be 'snake_case' and all class names must be 'PascalCase'. + static inline constexpr std::string_view + // coverity[autosar_cpp14_a15_5_2_violation] The design of the program does not raise the std::terminate exception. + // coverity[autosar_cpp14_a15_5_3_violation] The design of the program does not raise the std::terminate exception. + // coverity[exn_spec_violation] No try-catch is required because the design of the program does not raise exceptions. + _removeNamespaces(std::string_view sv) noexcept + { + // check that input end is not a space and start is not a colon + if (sv.empty() || sv.back() == ' ' || sv.front() == ':') + { + return sv; + } + + // position is currently at start of symbol + // e.g. ns::_detail::Class::fn + // ^------------------------ + std::size_t pos { 0U }; + + // detect namespaces by checking if start of input matches '[a-z0-9_]+::' + // everytime we match that regex, we remove that prefix + while (true) + { + std::size_t tempPos { pos }; + + // warning: technically C++14 does not guarantee that a-z numerical values are sequential + while ((tempPos < sv.size()) && ((sv[tempPos] >= 'a' && sv[tempPos] <= 'z') || + (sv[tempPos] >= '0' && sv[tempPos] <= '9') || (sv[tempPos] == '_'))) + { + ++tempPos; + } + + // check next characters are :: + // e.g. ns::_detail::Class::fn + // --^---------------------- + if ((tempPos <= sv.size() - 2U) && (sv[tempPos] == ':') && (sv[tempPos + 1U] == ':')) + { + tempPos += 2U; + pos = tempPos; + } + // no namespaces remaining + else + { + break; + } + } + + // position is currently after :: after last enclosing namespace + // e.g. ns::_detail::Class::fn + // -------------^----------- + + // return modified view + // coverity[autosar_cpp14_a15_4_2_violation] The design of the program does not raise the std::out_of_range exception. + return _trim(sv.substr(pos)); + } +}; + +} // namespace utils +} // namespace flatcfg + +#endif // FLATCFG_UTILS_PRETTY_FUNCTION_H diff --git a/src/flatcfg/src/flatcfg/utils/program_info.cpp b/src/flatcfg/src/flatcfg/utils/program_info.cpp new file mode 100644 index 0000000..a83b138 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/program_info.cpp @@ -0,0 +1,129 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "program_info.h" +#include "libc.h" +#include "logging.h" + +#include "flatcfg/fs/_os.h" +#include "flatcfg/flatcfg_error.h" + +#include "score/result/result.h" + +#include + +using score::Result; +using score::cpp::span; + +namespace flatcfg +{ +namespace utils +{ + +Result +ProgramInfo::executablePath(span outputBuffer) noexcept +{ + // unsupported buffer size -> nullopt + if (outputBuffer.empty()) + { + FLATCFG_LOG_WARN() << + "output buffer is empty"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + +#if FLATCFG_INTERNAL_HAS_LINUX + // read path of executable + auto sizeRes = utils::Libc::readlink("/proc/self/exe", outputBuffer); + if (!sizeRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, sizeRes.error().UserMessage()); + } + std::size_t size = *sizeRes; + + // check that size is smaller than output buffer, so we can null-terminate + // have to do it this way because readlink silently truncates + if (size >= outputBuffer.size()) + { + FLATCFG_LOG_WARN() << + "the size of the executable path (" << size << ") exceeds the size" + " of the output buffer (" << outputBuffer.size() << ")"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // null-terminate and return + outputBuffer[size] = '\0'; + return size; + +#elif FLATCFG_INTERNAL_HAS_QNX + // open file to read + auto fdRes = utils::Libc::open("/proc/self/exefile", O_RDONLY); + if (!fdRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, fdRes.error().UserMessage()); + } + int fd = *fdRes; + + // read contents of file + auto sizeRes = utils::Libc::read(fd, outputBuffer.data(), outputBuffer.size() - 1U); + if (!sizeRes)) + { + utils::Libc::close(fd); + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, sizeRes.error().UserMessage()); + } + std::size_t size = *sizeRes; + + // check we're at the end of the file + char buf[1] {}; + auto eofSizeRes = utils::Libc::read(fd, buf, 1U); + utils::Libc::close(fd); + if (!eofSizeRes) + { + return score::MakeUnexpected(FlatCfgErrorCode::kOsError, eofSizeRes.error().UserMessage()); + } + std::size_t eofSize = *eofSizeRes; + if (eofSize != 0U) + { + FLATCFG_LOG_WARN() << + "the executable path cannot be completely read into the output" + " buffer of size: " << outputBuffer.size(); + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // null-terminate and return + outputBuffer[size] = '\0'; + return size; + +#elif FLATCFG_INTERNAL_HAS_MACOS + // read process path + char pathBuf[PROC_PIDPATHINFO_MAXSIZE] {}; + const pid_t pid = utils::Libc::getpid(); + auto sizeRes = utils::Libc::proc_pidpath(pid, pathBuf, sizeof(pathBuf)); + if (!sizeRes)) + { + return return score::MakeUnexpected(FlatCfgErrorCode::kOsError, sizeRes.error().UserMessage()); + } + std::size_t size = *sizeRes; + + // check path isn't too big + if (size >= outputBuffer.size()) + { + FLATCFG_LOG_WARN() << + "the size of the executable path (" << size << ") exceeds the size" + " of the output buffer (" << outputBuffer.size() << ")"; + return score::MakeUnexpected(FlatCfgErrorCode::kBadLength); + } + + // copy over with null-terminator and return + span pathSpan { pathBuf, size }; + auto it = std::copy(pathSpan.begin(), pathSpan.end(), outputBuffer.begin()); + *it = '\0'; + return size; + +#else + #error Unsupported OS +#endif +} + +} // namespace utils +} // namespace flatcfg diff --git a/src/flatcfg/src/flatcfg/utils/program_info.h b/src/flatcfg/src/flatcfg/utils/program_info.h new file mode 100644 index 0000000..adc2a61 --- /dev/null +++ b/src/flatcfg/src/flatcfg/utils/program_info.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_UTILS_PROGRAM_INFO_H +#define FLATCFG_UTILS_PROGRAM_INFO_H + +#include "score/result/result.h" +#include "score/span.hpp" + +#include + +namespace flatcfg +{ +namespace utils +{ + +/// @brief Class containing helper functions to obtain runtime information +/// about the currently running program. +class ProgramInfo +{ +public: + /// @brief Get the absolute null-terminated path to the executable of the + /// current process. + /// @returns The size of the path written to the buffer (not including the + /// null terminator), or an error if the path cannot be found, if + /// the output buffer is empty, the buffer is not large enough to + /// hold the null-terminated path, or calling an OS function + /// results in an error. + /// @warning The path written to the output buffer may not exist. + static score::Result + executablePath(score::cpp::span outputBuffer) noexcept; +}; + +} // namespace utils +} // namespace flatcfg + +#endif // FLATCFG_UTILS_PROGRAM_INFO_H diff --git a/src/ptpd/BUILD b/src/ptpd/BUILD new file mode 100644 index 0000000..ddadd31 --- /dev/null +++ b/src/ptpd/BUILD @@ -0,0 +1,133 @@ +# src/ptpd/BUILD + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +# Define a genrule to create config.h +genrule( + name = "generate_config_h", + srcs = ["cmake/config_linux.h.in"], # Reference the config_linux.h.in file + outs = ["gen_config/config.h"], # Output to a generated directory + cmd_bash = """ + echo "Replacing BUILD_DATE" + echo "Reading $(SRCS)" + echo "Writing $(OUTS)" + sed -e "s/@BUILD_DATE@/$$(date +%Y%m%d)/" $(SRCS) > $(OUTS) + """, + output_to_bindir = True +) + +cc_library( + name = "config_h_lib", + hdrs = [":generate_config_h"], + strip_include_prefix = "gen_config", # This makes #include "config.h" work +) + +cc_library( + name = "ptpd_lib", + srcs = [ + "src/arith.c", + "src/bmc.c", + "src/constants.h", + "src/ptp_primitives.h", + "src/ptp_datatypes.h", + "src/datatypes.h", + "src/dep/constants_dep.h", + "src/dep/datatypes_dep.h", + "src/dep/ipv4_acl.h", + "src/dep/ipv4_acl.c", + "src/dep/msg.c", + "src/dep/net.c", + "src/dep/ptpd_dep.h", + "src/dep/eventtimer.h", + "src/dep/eventtimer.c", + "src/ptp_timers.h", + "src/ptp_timers.c", + "src/dep/servo.c", + "src/dep/iniparser/dictionary.h", + "src/dep/iniparser/iniparser.h", + "src/dep/iniparser/dictionary.c", + "src/dep/iniparser/iniparser.c", + "src/dep/configdefaults.h", + "src/dep/configdefaults.c", + "src/dep/daemonconfig.h", + "src/dep/daemonconfig.c", + "src/dep/startup.c", + "src/dep/sys.c", + "src/display.c", + "src/management.c", + "src/signaling.c", + "src/protocol.c", + "src/dep/ntpengine/ntp_isc_md5.c", + "src/dep/ntpengine/ntp_isc_md5.h", + "src/dep/ntpengine/ntpdcontrol.c", + "src/dep/ntpengine/ntpdcontrol.h", + "src/timingdomain.h", + "src/timingdomain.c", + "src/dep/alarms.h", + "src/dep/alarms.c", + "src/ptpd.c", + "src/ptpd.h", + "src/score/src/score_ptp_consumer.c", + "src/score/src/score_ptp_provider.c", + "src/score/src/score_ptp_common.c", + "src/score/src/score_ptp_arith.c", + "src/score/src/score_ptp_subtlv.c", + + "src/dep/eventtimer_posix.c", + "src/dep/statistics.h", + "src/dep/statistics.c", + "src/dep/outlierfilter.h", + "src/dep/outlierfilter.c", + ], + hdrs = glob([ + "src/**/*.h", + "src/**/*.def" + ]), + includes = [ + "src", + "src/def", + "src/def/message", + "src/def/managementTLV", + "src/def/signalingTLV", + "src/def/derivedData", + "src/score", + "src/score/src", + "src/score/inc", + "src/dep", + "src/dep/ntpengine", + "src/dep/iniparser" + ], + deps = [ + ":config_h_lib", + "//src/common", + "//src/tsync-ptp-lib", + "//src/tsync-utility-lib:tsync_utility" + ], + strip_include_prefix = "", + copts = [ + "-DHAVE_CONFIG_H", + '-DDATADIR=\\"/share\\"', + "-DPTPD_EXPERIMENTAL", + "-DPTPD_STATISTICS", + "-DPTPD_UNICAST_MAX=128", + "-DPTPD_PTIMERS", + "-D_GNU_SOURCE", + "-DHAVE_PCAP_PCAP", + "-DPTPD_PCAP" + ], +) + +cc_binary( + name = "ptpd", + deps = [ + ":ptpd_lib", + ], + linkopts = [ + "-lpthread", + "-lrt", + "-lm", + "-lpcap", + ] +) diff --git a/src/ptpd/COPYRIGHT b/src/ptpd/COPYRIGHT new file mode 100644 index 0000000..86a257d --- /dev/null +++ b/src/ptpd/COPYRIGHT @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2015 Wojciech Owczarek. + * Copyright (c) 2014 Perseus Telecom. + * Copyright (c) 2013-2014 Harlan Stenn, + * George N. Neville-Neil, + * Wojciech Owczarek, + * Jan Breuer. + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Wojciech Owczarek, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen. + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/src/ptpd/ChangeLog b/src/ptpd/ChangeLog new file mode 100644 index 0000000..775bd39 --- /dev/null +++ b/src/ptpd/ChangeLog @@ -0,0 +1,429 @@ +2013-10-23 Wojciech Owczarek + + * 2.3.2 release + + * Bug fixes / improvements since 2.3.1: + + - systemd service file added and RPM spec updated + to handle systemd + - fixed segfault when shutting down NTP time service + - ptpengine:always_respect_utc_offset is now enabled + by default, to protect from clock jumps when GM + stops announcing currentUtcOffsetValid flag + - re-arranged order of InterfaceInfo fields to + fix memory alignment issues (SF bug #70) + - corrected logMessageInterval field values + for hybrid mode (SF bug #69) + - fixed NTPd check failures resulting from signals + interrupting select() + - increased statistics file line buffer size to prevent + truncation and merging of lines + - added Sequence ID to statistics log + - Solaris build fixes - linking lnsl and lsocket when + building without SNMP support + - critical: added minimum POSIX timer interval to prevent from + timers firing to quickly for the process to handle, + resulting in 100% CPU and endless signal queue + - improved processing of unicast destination, domain + and local preference lists + - CPU affinity setting moved to a separate function + - fixed incorrect PTP_UNICAST flag setting for multicast + - fixed missing management or signalling message acceptance + condition (clockid==clockid, portid=0xffff): ISPCS 2015 plugfest, + fourth missing: (clockid=0x(f), portid==portid) + - signaling.c: fixed order of grant processing for PTP messages + (ann,syn,fup,dresp,pdelayresp) instead of numeric loop, + to assist with interop where transmission request order + makes a difference to the master + - signaling.c: do not reset known clock ID when changing GMs, + to allow to keep accepting announce from other masters + - signaling.c: update unicast index when transport address + match only: fix for cache misses for non-best masters + - daemonconfig:c - reworked all config mapping macros + to functions, parseConfig now handless all operations: + parsing, quiet parsing, reload check, help - no hidden + values in dictionary anymore + - closed sf bug #64 - leaving mcast group with 0x0 address + - peer multicast group only joined when needed, not always + - logMessage now keeps hash of the last message body, + preventing from same message being repeated + - null checks getInterfaceInfo set of helper functions + in case if some data returned by getifaddrs() is null: + fixes segfaults on some exotic interface types + - signaling.c: use message type <-> smaller index number + conversion so grant table is 5-element, not 16-element: + significantly reduced memory footprint + - added dummy Makefile to src/dep to allow make commands + from that directory + - added buffer overflow protection for management + TLVs: protects against read past message length, + and against read past buffer space + + * New features since 2.3.1: + + - support for user variables in configuration: + - variables specified variables:name=val, and + used as @name@ + - built-in variables: @pid@, @hostname@ + - support for configuration templates: + - built-in + - template files + - unicast port number mask to assist with nodes + using different port IDs for negotiation + - option to accept any GM when unicast negotiation + is enabled + - UDP checksums disabled on Linux by default: + enables interop with TCs that don't correct checksums + - Added QNX support: + - clean build on QNX 6.5 out of the box + - adjfreq emulation using native ClockAdjust + - binding to CPU cores + - Experimental (but yield best results): + * interpolated getTime attached to IRQ0 + * RX/TX timestamps in software on send or receive, + bypassing pcap or socket timestamping + - support for systems without getopt_long (like QNX) + - adjfreq-like behaviour on systems with adjtime only: + properly scaled adjtime() pulled into PI servo + (vastly improved sync on systems like OpenBSD and OSX), + support for drift file for those systems + - fixed preserving incorrect master address when + best master lowers priority (GM change as a result + of announce from current master, not foreign) + - PTP MIB enhanced with string values, counter support, + clearing counters and more, added some missing items + from previously supported items + - SNMP trap support for common alarm conditions + - enhanced management message support: enable/disable port, + some missing values, PTPd now properly applies + configuration when receiving SET messages + - added simple SNMP poller script: snmptpq + - SNMP MIB now using PTPd's own Enterprise number + +2013-06-10 Wojciech Owczarek + + * 2.3.1 release + + * New features since 2.3.0: + + - full unicast support both in master and in slave mode: + + support for multiple unicast destinations for masters + + feature-complete Telecom Profile support: + * unicast negotiation for masters and slaves + * disabling BMCA for masters (also works for multicast) + * support for slaves with masters in multiple domains + * influencing slave BMCA with local preference + * serving multiple Telecom slaves at different rates + * slaves correctly negotiate message rates over supported + range of intervals until an acceptable interval is granted + + support for unicast Peer Delay target - exotic use case + - greatly improved network robustness + - added support for slave-only builds (configure --slave-only): + allows building a binary incapable of running as master + - simple failover mechanism - ptpengine:backup_interface + - ptpengine:max_delay_max_rejected setting to unblock ptpd + rejecting all messages when max_delay set + - ptpengine:max_delay_stable to only use max_dely when clock + is stable + - selectable log timestamp format (unix or datetime) + - ptpd now compiles and runs out of he box on NetBSD, OpenBSD, FreeBSD, + OSX and Solaris and its derivatives (OpenIndiana, OmniOS etc.) + - major rework of the ACL code + - outlier filter auto-tune to improve operation in noisy environments(high PDV) + - added support for statistical filters (min/mean/median) for sync + and delay messages - min or median can be used in envifonments with high PDV + - added small step detection (millisecond level) + - added ptpengine:clock_update_timeout setting to reset slave + if no clock updates for a period of time + - when running slave-only in unicast mode, unicast address + does not have to be specified - ptpd will wait for a GM and latch onto it + - automatic Delay Request override for Hybrid and Unicast modes + - added options allowing to step the clock on startup + - improved sequence and re-ordering checks + - added support for "any domain" operation where ptpd will sync with + any available domain + - idle timeout (PTPd hands over clock control) + - clock update timeout (PTPd restarts slave on timeout) + - maximum state machine reset count - full network restart when exceeded: + allows ptpd to survive situations where network interface is removed, + shut down or IP address changes + - support for NIST/IETF leap seconds file both for master and slave + - configurable leap second handling: accept, ignore, step + - support for leap second smear for slave operation + - option to step clock on startup + - TimingDomain API: all NTP failover code removed + in favour of a generic interface for multiple time controllers. + - configurable port number - useful when running multiple instances + - periodic log file updates with slave or master information - separate + from statistics file: allows sending statistics into syslog + - support for software transmit timestamping on Linux + - lookup tables used for matching Sync and FollowUp when + no transport information available + - added periodic status updates to log file (global:periodic_updates) + - status file will always display counters for common error events + if they are non-zero (includes denied unicast grants and ignored + Announce resulting from rejected UTC properties) + - reporting on message receive and transmit rates + in the status file + - restored compatibility with 802.1AS + + * Bug fixes / improvements since 2.3.0: + + - added CentOS / RHEL 7.x support + - statistics code enabled by default - can be disabled + - NTPd failover code enabled by default - can be disabled + - Improved status file - more error conditions included, + message performance statistics included + - Rewritten timer subsystem allowing for easily implementing + other implementations: included is the old interval timer implementation, + and new (now default), POSIX timer implementation. This allows for + fully standards-compliant message rates. This can be disabled with + a configure flag + - ptpd now usable on systems with no adjtimex() call like OSX, + OpenBSD and LynxOS () + - moving standard deviation algorithm fix improving the outlier + filters + - major servo.c spaghetti code cleanups + - network code cleanup + - fixed bug #66 - Ethernet interfaces no longer need IP addresses + to run PTP Ethernet transport + - fixed bug / feature request #21: timestamping initialisation on + PHC kernels + - fixed bug where ptpd would cause 100% CPU usage because of a + late TX timestamp + - fixed bug / feature request #22: prevent FMR from overwriting + best master + - improved servo stability detection: servo no longer reporting + stable when discarding most samples or heavily slewing + - ptpd will now join and leave multicast groups only when running + multicast or hybrid: allows running unicast and multicast + independently + - ptpd will now ignore unicast packets in multicast-only mode + and vice versa. In hybrid mode, only unicast Delay Request / + response is accepted; multicast management messages are always + accepted + - further autoconf improvements across all supported OSes + - added missing files to distribution + - ptpd will now recover even from critical network failures, + such as reloading drivers + - remaining memory leaks fixed + - more code cleanup, no more ajtimex() and similar calls in protocol.c + - some refactoring, constified rtOpts wherever possible + - less "dramatic" warnings about drift file + and clock stability detection + - fixed unnecessary TX timestamp attempts in PCAP mode + - fixed issue where drift file would not be correctly + written on exit under certain conditions + - other minor bugs fixed + +2013-11-22 George Neville-Neil , Wojciech Owczarek + + * 2.3.0 release + + * New Features: + + - IEEE 802.3 Support + - Support for reading from the Berkeley Packet Filter, resulting in lower jitter + - Configuration file support, multiple previously unsupported options + - Rewritten command line parameter handling, new parameters + - Command line options to print default configuration, print lock file, check configuration and more, + - Full help (-H) for all configuration options, + - Configuration reload support with SIGHUP, + - Rewritten logging subsystem, + - Separate log file and statistics file, SIGHUP reprints the headers + - Support for mode and interface specific lock files + - Network interface checks before startup and during config reload + - Added ./configure --sigusr2=counters - will dump PTP engine countersto current log target when SIGUSR2 received + - Support for presets - groups of PTP protocol options defining ptpd behaviour + presets available: slaveonly, masteronly, masterslave + - Support for tick adjustment as well as frequency + maximum frequency offset is now configurable and can be above 512 ppm - tick is used above 512 ppm, + - Added a compatibility option to always honour the UTC offset announced by a GM when in slave state + - Added an announce receipt timeout "grace period" for slave state + slave can wait n times timeout without fully resetting + allows a seamless failover without resetting delay values etc., + - Added support for DELAY_DISABLED delay mode - syntonisation only + - Initial support for informing the clock subsystem about the sync status + STA_UNSYNC is unset when adjusting the clock (allows syncing hardware RTC clocks with the system clock) + also maxerror and esterror are set, + - Support for RTC clock control on Linux + - Support for legacy command line switches from previous versions + - Configurable log level for normal logs, not only debug logs. + - Generic log file handler mechanism with a simple built-in logrotate implementation + for each log file container you can specify max size and max number of files + - Extensive support for realtime statistics + online long term statistics and moving statistics + mean and std dev. + Some new features included depend on statistics support, enabled with the --enable-statistics + configure switch. Statistics rely on double precision so may be disabled for simple embedded systems + - Outlier filters based on Peirce's criterion + blocks / filters spikes from delayMS and delaySM + - Generic PI controler model + - P and I components changed from attenuations to gains + - Double precision servo allows for tighter clock control + - Clock stabilisation detection based on the standard dev of observed drift + - Clock "calibration delay" + allows a configurable delay between seeing a GM for the first time and enabling clock control + - Realtime statistics - mean and std dev of one-way delay, ofm and observed drift + - Status file: one-screen dump of information about the current state of ptpd, updated in configurable intervals. + - Support for "panic mode" + if ptpd sees offset from master above 1 second, it will pause clock updates for a given amount of time + - NTPd integration and failover + ptpd can now connect to local ntpd using mode 7 packets (code derived from ntpdc) + and using authenticated requests can enable/disable ntpd clock control. + Enable with configure --enable-ntpdc + - Dump counters + clear counters using a SIGUSR2 handler to dump all counters to the log file + configure --enable-sigusr2=counters + - Support for ACLs for management and timing messages + - Basic support for SO_TIMESTAMPING (RX, TX) on Linux + - Support for non-Ethernet interfaces on Linux (Infiniband, GRE etc.) + - Support for offset / delay asymmetry correction + - Ptpd can now prefer or require GMs with valid UTC time + - Support for setting IP DSCP values + - Periodic IGMP joins also in master mode - improved robustness against network port failures + - Support for binding PTPd to a CPU core + - Control over tick rate on Linux, allowing faster clock slewing if needed + + + + * Bug fixes: + + - TTL no longer defaults to 64 in P2P mode - in P2P it's set to 1, + - Fixed an issue whereby observed drift would be reset to 0 if it + could not be read from the drift file (empty or nonexistent drift file), + now the kernel value is used if read from file fails. + - Support for updating utmp/wtmp when stepping the clock (FR #38) + - Mean Path Delay filtering now done in float (patch #49) + - Multiple other SourceForge bugs closed: Bugs closed: + + #25, #34, #26, #42, #39, #33, #46, #45, #28, + #27, #48, #54, #47, #40, #37, #53, #50, #52, #59, #60 + + * Cleanups and other changes: + + - Major startup.c cleanup, removed the parallel daemon checks, + in favour of better lock handling and the automatic lock file + name support, + - Some minor refactoring, + - Added a TimePropertiesDS structure and removed its fields + from PtpClock + - Improved log file format with full date and interface name, + removed the "===" indents + - Delay Request interval no longer mandatory in hybrid mode, + but a warning will be issued if it's not explicitly defined - + default of 0 is used, + - Removed the experimental status of hybrid mode, + - Manual stepping of the clock no longer just resets the observed + drift: resets it or restores it based on the drift handling setting, + and sets the time immediately before writing any logs - this allows + to re-sync and stabilise time almost instantly + - Up to date man pages written + +2012-06-20 George Neville-Neil + + * 2.2.2 release + + * Leap second fix + +2012-01-05 George Neville-Neil + + * 2.2.0 release + + * Patches: 3134556, 3296405 + + * Added support for Mac OS X (tested on Snow Leopard and Lion) + + * Protocol implementation Fixes: + + * Bugs Fixed + + - Client now correctly accepts the Master DelayReq rate + - DelayMS and delaySM now correctly show the sign when negative + - Sanity Flags: client now requires -g or -G for slave/master operation. + - Client can print the MAC address of its master (instead of EEUI) + - master now sends ARB timescale only with utc_offset of zero + - slave now only uses the last UTC_Offset if UTC_Valid bit is on. + - passive masters no longer become active every 12s, + - first delayreq is postponed until we receive the first sync + - -G (master with ntpd) now announces a better clock class of 13 + - delayReq period is now uniformly randomized from range + - updated to the PTPv2 messages rates (sync / delayreq / announce ) + - operator is warned once when the we slew the clock at maximum speed + - and several others too minor too mention + + * System fixes and new features + + - Frequency adjustment is now calculated in double precision + - Kernel timestamps are now in nanoseconds precision + - Timer system overhead was reduced to 16 alarms per second (was 1000) + - each reset now generates an IGMP leave/join operation to + the multicast group + - Log file is now appended, with the right permissions + - Debug messages show a timestamp + - Signals are now processed synchronously (to avoid race conditions) + - Configurable amount of logging (to avoid filling up /var/log) + - client now checks own filelock, $0 and well-known daemons. + - unicast messages can use DNS + - syslog support (-S) + - quality file can be generated with received syncs (-R) + - messages can be dumped for debug (-P) + - gnore packets that result in large deltas (-M) + - SIGUSR1 now steps the clock to the current PTP offset + - SIGUSR2 now cycles the domain number (useful for testing) + - reverted R135 timer change from integer back to floating point + - rand() is now seeded with last digits of our own mac address + - IGMP_refresh waits 100ms between drop() and add() + - checked to run without leaks inside valgrind 3.5.0 + - last message received is identified by a column on the statistics log + - messages are sent to Syslog by default. reversed -S flag + - statistcs file now display /etc/ethers names (besides mac address) + - option -C is console mode with full verbosity + - startup warnings are also duplicated in stdout + - startup: lockfile is checked twice: once at init, to return + correct errorlevel, and a second time after deaemon() + - check for root uid() + - improvements in parallel daemons checking + - command line parameters are dumped at init + - Set the unicast flag when sending unicast packets + (experimental, hybrid mode only). + + - Reimplemented integer64_to_internalTime not to use doubles + - Replaced divTime by div2Time + - Replaced all time.seconds = time.nanoseconds = 0 by clearTime(&time) + - Replaced all hex values by named flags + - Optimized comparison of clockIdentity in bmc.c + - Resolved issue of comparison of offsetScaledLogVariance + - Optimized bmcStateDecision not to call bmcDataSetComparison so often with the same parameters + - displayStats now uses getTime instead of gettimeofday + +2011-02-01 George Neville-Neil + + * Add support for DNS lookup of timeserver for unicast. + + * Add support for unicasting delay requests. + + * Add code to dump packets on demand via the -P flag as well as in + response to updates that violate either the -M or -O flags. + +2010-10-12 George Neville-Neil + + * 2.1.0 First main line release of PTPv2 code base + (IEEE-1588-2008) + + * Add code to limit how much of an offset or delay the client is + willing to tolerate. + + * Add support for BINTIME on FreeBSD which gives more accurate + packet timestamps. + + * Add quality file support + + * Fix significant bugs that prevented correct operation in + End-to-End mode. + + * Add support for syslog. + + * Add support for user configurable TTL. + + * Clean up code formatting, headers, comments etc. + diff --git a/src/ptpd/Doxyfile b/src/ptpd/Doxyfile new file mode 100644 index 0000000..961d7e5 --- /dev/null +++ b/src/ptpd/Doxyfile @@ -0,0 +1,2492 @@ +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = score-ptp-daemon + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = _doc/doxy + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = .h=C++ + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = _doxygen_warnings.log + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ./src ./include ./tests + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = ./_* + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /
+ + + +
+

+Last site update: 22 November 2013 | Latest PTPd version: 2.3.0 +| PTPd is hosted by +SourceForge.net Logo +

+
+
+ + + + + +
+
PTPd
+
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

The PTP daemon (PTPd) implements the Precision Time protocol (PTP) +as defined by the relevant IEEE 1588 standard. PTP Version 2 implements +IEEE-1588-2008. PTP was developed to provide very precise time +coordination of LAN connected computers.

+ +

Documentation is now being added to the +project Wiki .

+ +

PTPd is a complete implementation of the IEEE 1588 specification +for a standard (non-boundary) clock. PTPd has been tested with and is +known to work properly with other IEEE 1588 implementations. The +source code for PTPd is freely available under a BSD-style +license. Thanks to contributions from users, PTPd is becoming an +increasingly portable, interoperable, and stable IEEE 1588 +implementation.

+ +

PTPd can run on most 32-bit or 64-bit, little- or big-endian processors. It +does not require an FPU, so it is great for embedded processors. PTPd +currently runs on Linux, uClinux, FreeBSD, and NetBSD. It should also +be easy to port to other platforms.

+ +

PTPd is free. Everyone is welcome to use and contribute to PTPd.

+ +

The PTPd project would like to thank the following for their kind + donations of equipment and time: + +

+
Meinberg Corp +
Donation of an m600 PTPv2 GPS Grandmaster Clock +
Sentex Data Communications +
Hosting the FreeBSD Network Test Cluster +
The FreeBSD Foundation +
Providing testing resources for the PTPd Project +
+
+ +

PTPd version 1 implements IEEE-1588-2002 and is considered a legacy +protocol. 1.1.0 + + +

+ + + + diff --git a/src/ptpd/doc/ptpd-2.3.0-migration-guide.html b/src/ptpd/doc/ptpd-2.3.0-migration-guide.html new file mode 100644 index 0000000..d1dfea2 --- /dev/null +++ b/src/ptpd/doc/ptpd-2.3.0-migration-guide.html @@ -0,0 +1,243 @@ + + + + + + +PTPd + + + + +

PTPd 2.2.x → 2.3.0 migration guide

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old option (2.2.2)New option (2.3.0)Meaning
-H-H --long-helpshow detailed help page
-g-s --slaveonlyrun as slave only
-G-M --masteronlyMaster, passive when not best GM
-W-m --masterslaveMaster, slave when not best GM
-b NAME-i --interfaceInterface to use
-c-C --foregroundDon't run in background
-C-V --verboseRun in verbose foreground mode
-f FILE-f --log-file
+-S --statistics-file
Log file Statistics file
-S--global:use_syslog=NDON'T send messages to syslog
-T NUM--ptpengine:multicast_ttlset multicast time to live
-D(always in .csv format)display stats in .csv format
-P display each received packet in detail
-R FILE--global:quality_filerecord data about sync packets in a seperate file
-x(no alternative)do not reset the clock if off by more than one second
-O NUM--servo:max_offset
+see also ptpengine:sync_outlier_filter_*
do not reset the clock if offset is more than NUMBER nanoseconds
-t-n --noadjustDo not adjust the clock
-M NUMsetup by ptpengine:delay_outlier_filter_* optionsdo not accept delay values of more than NUMBER nanoseconds
-a NUM,NUM--servo:kp --servo:kispecify clock servo Proportional and Integral components → converted from attenuations to gains
-w NUM--servo:delayfilter_stiffnessspecify one way delay filter stiffness
-u-u --unicastUnicast mode
-U-y --hybridHybrid mode
-e--ptpengine:transport=ethernetrun in ethernet mode
-h-E --e2erun in End to End mode
-z-P --p2prun in Peer-delay mode
-l NUM,NUM--ptpengine:inbound_latency
+--ptpengine:outbound_latency
inbound, outbound latency in nsec.
-o NUM--ptpengine:utc_offsetcurrent UTC offset
-i NUM-d --domainPTP domain number (between 0-3)
-n NUM--ptpengine:log_announce_intervalannounce interval
-N NUM--ptpengine:announce_receipt_timeoutannounce receipt timeout
-y NUM--ptpengine:log_sync_intervalsync interval
-m NUM--ptpengine:foreignrecord_capacitymax number of foreign master records
-v NUM--ptpengine:ptp_allan_varianceMaster mode: specify system clock Allen variance
-r NUM--ptpengine:ptp_clock_accuracyMaster mode: specify system clock accuracy
-s NUM--ptpengine:clock_classMaster mode: specify system clock class
-p NUM--ptpengine:priority1Master mode: specify priority1 attribute
-q NUM--ptpengine:priority2Master mode: specify priority2 attribute
-Y 0[,0]-a --delay-override
+-r --delay-interval
Initial and Master_Overide delayreq intervals
-B-D<DD...> --debugEnable debug messages
-j--ptpengine:igmp_refresh=NDo not refresh the IGMP Multicast menbership at each protol reset
-L-L --ignore-lock
+see --ptpengine:pid_as_clock_idendity
+see --auto-lock +
Allow multiple instances
-V 0--global:status_update_interval
+--global:statistics_log_interval
Seconds between log messages
+ + diff --git a/src/ptpd/doc/ptpd_2005_1588_conference_paper.pdf b/src/ptpd/doc/ptpd_2005_1588_conference_paper.pdf new file mode 100644 index 0000000..3d4f80d Binary files /dev/null and b/src/ptpd/doc/ptpd_2005_1588_conference_paper.pdf differ diff --git a/src/ptpd/docs/_static/custom.css b/src/ptpd/docs/_static/custom.css new file mode 100644 index 0000000..711ccad --- /dev/null +++ b/src/ptpd/docs/_static/custom.css @@ -0,0 +1,32 @@ +tr.needs_skipped td.needs_result p{ + background-color: #1b6082; + padding: 3px 5px; + text-align: center; + border-radius: 10px; + border: 1px solid #212174; + color: #ffffff; +} + +tr.needs_failure td.needs_result p { + background-color: #c2162d; + padding: 3px 5px; + text-align: center; + border-radius: 10px; + border: 1px solid #212174; + color: #ffffff; +} + +tr.needs_passed td.needs_result p { + background-color: #1b822c; + padding: 3px 5px; + text-align: center; + border-radius: 10px; + border: 1px solid #212174; + color: #ffffff; +} + + +.wy-nav-content +{ + max-width: 70% !important; +} diff --git a/src/ptpd/docs/architecture/architecture.rst b/src/ptpd/docs/architecture/architecture.rst new file mode 100644 index 0000000..32becfc --- /dev/null +++ b/src/ptpd/docs/architecture/architecture.rst @@ -0,0 +1,152 @@ +##################### +Software Architecture +##################### + +******************* +Required Interfaces +******************* + +- The ``score-ptp-daemon`` is a background daemon process that receives/sends the PTP packets over Ethernet - facilitating + Time Synchronization over Ethernet. +- The PTP messages are forwarded to the AUTOSAR Adaptive TimeSynchronization stack via the ``tsync-ptp-lib`` module. +- ``tsync-ptp-lib`` provides the following APIs that help facilitate the forwarding of time messages - + + +-------------------------------------------+-----------------------------------------------------+ + | **API Name** | **Description** | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_Open | API to perform basic initialization of the | + | | tsync-ptp-lib. | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_Close | API to shutdown the tsync-ptp-lib. | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_OpenTimebase | API to open a timebase for reading and writing. | + | | (For every configured time base) | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_CloseTimebase | API to close and invalidate the given timebase | + | | handle. | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_BusSetGlobalTime | API to allow the Time Base Provider Modules to | + | | forward a new Global Time tuple (i.e., the Received | + | | Time Tuple) to the tsync-ptp-lib | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_BusGetGlobalTime | API to allow the Time Base Provider Modules to | + | | read new Global Time tuple from tsync-ptp-lib | + | | and send over Ethenet. | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_GetCurrentVirtualLocalTime | API to read Virtual Local Time of the referenced | + | | Time Base. | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_RegisterTransmitGlobalTimeCallback | API to register trigger transmit callback. | + | | tsync-ptp-lib will call this callback to trigger | + | | immediate time transmission. | + +-------------------------------------------+-----------------------------------------------------+ + | TSync_GetTimebaseConfiguration | API to read configuration parameters | + | | from score::time. | + +-------------------------------------------+-----------------------------------------------------+ + + +************************ +Autosar TLVs and SubTLVs +************************ + +Overview +======== + +- To enable the Autosar TLV and SubTLVs, please refer the product documentation of ``tsync-daemon`` from the AUTOSAR + Adaptive layer. +- When they are enabled, the ``FollowUp`` packet will be appended with both Autosar TLV and configured SubTLVs. +- They are briefly explained below: + +Autosar TLV +----------- + +- Except for the ``lengthField``, all the other field values are static. +- The values are provided in the Protocol Specification, and explained in detail as part of the :ref:`Product Documentation` + +Autosar SubTLV +-------------- + +- Each SubTLV payload is individually configurable. However, the order of the SubTLV is always the same. +- Few SubTLVs can be "Secured" if CRC is globally enabled via the configuration parameter ``CRC-SECURED`` (in case of + Providers) and ``CRC-VALIDATED`` (in case of Consumers) in arxml. + +SubTLV Types +============ + +There are 5 types of SubTLVs: + +- Time Secured +- UserData Secured/Non-Secured +- Status Secured/Non-Secured (out-of-scope, see below) +- OFS Secured/Non-Secured (out-of-scope, see below) + +Time Secured +------------ + +- Always Secured, no "Unsecured" variant. +- Used to detect the integrity of the "time values" in FollowUp messages. +- The SubTLV is available only when the CRC is globally enabled in the configuration. +- Since there is only CRC enabled version of this SubTLV, if the CRC is not enabled globally, this SubTLV will not be included. +- Here are the list of "time values" that are considered for the CRC computation. + + - messageLength + - domainNumber + - correctionField + - sourcePortIdentity + - sequenceIdentity + - preciseOriginTimestamp + +- There are 2 CRCs associated with this SubTLV - ``CRC_Time_0`` and ``CRC_Time_1`` + + - ``CRC_Time_0`` is computed using ``domainNumber``, ``sourcePortIdentity`` and ``preciseOriginTimestamp`` + - ``CRC_Time_1`` is computed using ``messageLength``, ``correctionField`` and ``sequenceId`` + +.. note:: + Not all the "time values" are necessarily used. Only the ones that are configured will be considered for the CRC computation. + +Userdata Secured/Not-Secured +---------------------------- + +- Can be Secured or Not-Secured +- Used to transmit 3 bytes of user defined values. +- The SubTLV can be CRC protected if CRC is enabled globally. + +Status Secured/Not-Secured +-------------------------- + +- Can be Secured or Not-Secured, used to transmit the status of Sync-To-Gateway bit. +- The SubTLV can be CRC protected if CRC is enabled globally. + +.. important:: + Since the Gateway use-case is not supported by AUTOSAR Adaptive as of today, this SubTLV is out-of-scope for now. + +OFS Secured/Not-Secured +----------------------- + + - Out of scope, since Offset timebases are slated to be deprecated in future AUTOSAR Adaptive standard releases. + + +**************** +Dynamic Behavior +**************** + +Consumer Functionality +====================== + +- When the PTP frame arrives over Ethernet, its packet length is first checked to ensure it matches the expected length. +- If the preliminary checks are successful, the time information will be processed. +- Since the Adaptive Applications need this time information, they must be forwarded to the Adaptive TSYNC stack. +- ``tsync-ptp-lib`` acts as the interface between ``score-ptp-daemon`` and the Adaptive Application. + +.. uml:: diagrams/consumer_reception.puml + +Provider Functionality +====================== + +- When acting as the Provider, the ``score-ptp-daemon`` has to transmit the messages over Ethernet periodically. +- In this case, the time information comes from the Adaptive Applications. +- Hence, the daemon uses the interfaces provided by ``tsync-ptp-lib`` to fetch the time from the Adaptive Application. + +.. uml:: diagrams/provider_periodic_transmission.puml + +.. uml:: diagrams/provider_immediate_transmission.puml diff --git a/src/ptpd/docs/architecture/diagrams/consumer_reception.puml b/src/ptpd/docs/architecture/diagrams/consumer_reception.puml new file mode 100644 index 0000000..e1cc9c6 --- /dev/null +++ b/src/ptpd/docs/architecture/diagrams/consumer_reception.puml @@ -0,0 +1,29 @@ +@startuml +title consumer_reception + +actor "physical-ethernet" as physical_ethernet order 1 #lightgreen +participant "score-ptp-daemon" as tsync_ptp_daemon order 2 #lightgrey +participant "tsync-ptp-lib" as tsync_ptp_lib order 3 #lightgrey + +physical_ethernet -> tsync_ptp_daemon: Sync Message +note left #lightyellow + Arrival of the Sync message on Ethernet +end note +tsync_ptp_daemon -> tsync_ptp_lib: TSync_GetCurrentVirtualLocalTime() +tsync_ptp_lib --> tsync_ptp_daemon: return +tsync_ptp_daemon -> tsync_ptp_daemon: Store the "VirtualLocalTime" \nof the Sync Message + +physical_ethernet -> tsync_ptp_daemon: FollowUp Message +note left #lightyellow + Arrival of the FollowUp message on Ethernet +end note +tsync_ptp_daemon -> tsync_ptp_lib: TSync_GetCurrentVirtualLocalTime() +tsync_ptp_lib --> tsync_ptp_daemon: return +tsync_ptp_daemon -> tsync_ptp_daemon: Store the "VirtualLocalTime" \nof the FollowUp Message + +tsync_ptp_daemon -> tsync_ptp_daemon: Compute the GlobalTime and validate the SubTLVs received. +tsync_ptp_daemon -> tsync_ptp_lib: TSync_BusSetGlobalTime() +tsync_ptp_lib --> tsync_ptp_daemon: return +tsync_ptp_lib --> tsync_ptp_lib: The tsync-ptp-lib updates the GlobalTime \nand the VirtualLocalTime \nof the FollowUp message \ninto the shared memory + +@enduml \ No newline at end of file diff --git a/src/ptpd/docs/architecture/diagrams/provider_immediate_transmission.puml b/src/ptpd/docs/architecture/diagrams/provider_immediate_transmission.puml new file mode 100644 index 0000000..b0f1b77 --- /dev/null +++ b/src/ptpd/docs/architecture/diagrams/provider_immediate_transmission.puml @@ -0,0 +1,49 @@ +@startuml +title provider_immediate_transmission + +participant "score-ptp-daemon" as tsync_ptp_daemon order 2 #lightgrey +actor "tsync-ptp-lib" as tsync_ptp_lib order 3 #lightgrey +actor "physical-ethernet" as physical_ethernet order 1 #lightgreen + + + +group ImmediateTransmission + tsync_ptp_daemon -> tsync_ptp_lib: Register the callback\nby calling the function\n TSync_RegisterTransmitGlobalTimeCallback()\n during initialization + + tsync_ptp_lib --> tsync_ptp_daemon: Callback to indicate immediate time transmission. + + tsync_ptp_daemon -> tsync_ptp_lib: TSync_BusGetGlobalTime() + tsync_ptp_lib --> tsync_ptp_lib: The tsync-ptp-lib reads the GlobalTime \nand the VirtualLocalTime \nfrom the the shared memory\nand sends it back to score-ptp-daemon + tsync_ptp_lib --> tsync_ptp_daemon: return + tsync_ptp_daemon -> tsync_ptp_daemon: Record the T0Vlt + note left #lightyellow + T0Vlt corresponds to the + VirtualLocalTime when the + GlobalTime was read. + end note + tsync_ptp_daemon -> physical_ethernet: Send Sync Message + + tsync_ptp_daemon -> tsync_ptp_lib: TSync_GetCurrentVirtualLocalTime() + tsync_ptp_lib --> tsync_ptp_daemon: return + note left #lightyellow + Soon after the Sync Message + is sent, fetch + VirtualLocalTime again. + end note + tsync_ptp_daemon -> tsync_ptp_daemon: Store this VirtualLocalTime as T4Vlt and \nuse it to compute PreciseOriginTime. + note left #lightyellow + The PreciseOriginTime is + computed using GlobalTime, + T0Vlt and T4Vlt. + end note + + tsync_ptp_daemon -> physical_ethernet: Send the FollowUp Message + +end group + +@enduml \ No newline at end of file diff --git a/src/ptpd/docs/architecture/diagrams/provider_periodic_transmission.puml b/src/ptpd/docs/architecture/diagrams/provider_periodic_transmission.puml new file mode 100644 index 0000000..4d2c3d0 --- /dev/null +++ b/src/ptpd/docs/architecture/diagrams/provider_periodic_transmission.puml @@ -0,0 +1,58 @@ +@startuml +title provider_periodic_transmission + +actor "physical-ethernet" as physical_ethernet order 1 #lightgreen +participant "score-ptp-daemon" as tsync_ptp_daemon order 2 #lightgrey +participant "tsync-ptp-lib" as tsync_ptp_lib order 3 #lightgrey + + + +group PeriodicTransmission + + note left #lightyellow + Sends Sync and FollowUp periodically + end note + tsync_ptp_daemon -> tsync_ptp_lib: TSync_BusGetGlobalTime() + tsync_ptp_lib --> tsync_ptp_lib: The tsync-ptp-lib reads the GlobalTime \nand the VirtualLocalTime \nfrom the the shared memory\nand sends it back to score-ptp-daemon + tsync_ptp_lib --> tsync_ptp_daemon: return + tsync_ptp_daemon -> tsync_ptp_daemon: Record the T0Vlt + note left #lightyellow + T0Vlt corresponds to the + VirtualLocalTime when the + GlobalTime was read. + end note + tsync_ptp_daemon -> physical_ethernet: Send Sync Message + + tsync_ptp_daemon -> tsync_ptp_lib: TSync_GetCurrentVirtualLocalTime() + tsync_ptp_lib --> tsync_ptp_daemon: return + note left #lightyellow + Soon after the Sync Message + is sent, fetch + VirtualLocalTime again. + end note + tsync_ptp_daemon -> tsync_ptp_daemon: Store this VirtualLocalTime as T4Vlt and \nuse it to compute PreciseOriginTime. + note left #lightyellow + The PreciseOriginTime is + computed using GlobalTime, + T0Vlt and T4Vlt. + end note + + tsync_ptp_daemon -> physical_ethernet: Send the FollowUp Message + note left #lightyellow + The FollowUp Message + contains PreciseOriginTime. + If configuration parameter + "messageCompliance" was set to + TSYNC_MC_IEEE8021AS_AUTOSAR, + then the Message will also have + Autosar TLV followed by + Autosar SubTLV(configuration based). + end note + +end group +@enduml diff --git a/src/ptpd/docs/delivery_notes/copyright.rst b/src/ptpd/docs/delivery_notes/copyright.rst new file mode 100644 index 0000000..fb2918a --- /dev/null +++ b/src/ptpd/docs/delivery_notes/copyright.rst @@ -0,0 +1,55 @@ +######### +COPYRIGHT +######### + +.. _ptpd_copyright: + +.. code:: rst + + Copyright (c) 2015 Wojciech Owczarek. + Copyright (c) 2014 Perseus Telecom. + Copyright (c) 2013-2014 Harlan Stenn, + George N. Neville-Neil, + Wojciech Owczarek, + Jan Breuer. + Copyright (c) 2011-2012 George V. Neville-Neil, + Steven Kreuzer, + Martin Burnicki, + Jan Breuer, + Wojciech Owczarek, + Gael Mace, + Alexandre Van Kempen, + Inaqui Delgado, + Rick Ratzel, + National Instruments. + Copyright (c) 2009-2010 George V. Neville-Neil, + Steven Kreuzer, + Martin Burnicki, + Jan Breuer, + Gael Mace, + Alexandre Van Kempen. + + Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + + All Rights Reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/ptpd/docs/delivery_notes/delivery_notes.rst b/src/ptpd/docs/delivery_notes/delivery_notes.rst new file mode 100644 index 0000000..2d24375 --- /dev/null +++ b/src/ptpd/docs/delivery_notes/delivery_notes.rst @@ -0,0 +1,142 @@ +############## +Delivery Notes +############## + +************* +1.3.7 release +************* + +Minor +===== + +- The ``syncPeriod`` for the Time Master is now sourced from the timesync configuration. The command-line support for this parameter has been deprecated. +- The configuration parameter ``followUpTimeoutValue`` is used to ensure the follow-up message is received within the specified time frame. +- The configuration parameter ``globalTimeSequenceCounterJumpWidth`` is used to detect the sequence-counter-jumps. + If a detected jump in any sync message exceeds the configured threshold, the associated PTP messages are rejected. +- *Infrastructure:* Migrate to ctc-bin 3.0.0 + + +************* +1.3.5 release +************* + +Minor +===== + +- The requirement to run aptpd2 as root has been removed. It can now be executed with non-root access. +- The `on` command is no longer necessary to launch aptpd2. + + +************* +1.3.1 release +************* + +Defect Fix +========== + +- Improved error handling and stability in the context of network device discovery (AZB #79872) + + +************* +1.3.1 release +************* + +New Features +============ +- Enabled the build for QNX8 SDP + + +************* +1.3.0 release +************* + +New Features +============ +- Enabled the System Clock update feature in the Consumer mode. The Clock will get updated with every successful reception + of the sync/follow-up PTP message pair. + +Defect Fix +========== +- Update the call to ``TSync_GetTimebaseConfiguration()`` API to accept the ptp-daemon role - Provider or Consumer (AZB #77926) + + +************* +1.2.7 release +************* + +Minor +===== +- Update the detailed design and extend SW architecture documentation with sequence diagrams for PTP message + transmission and reception + + +************* +1.2.5 release +************* + +Minor +===== +- Update product documentation with PTP ``Sync`` and ``Followup`` message format layouts according to AUTOSAR +- Finalize SubTLV message handling after tests with different configuration permutations + + +************* +1.2.4 release +************* + +Minor +===== +- All the SubTLVs except Status and OFS are now supported +- CRC support is introduced for the SubTLVs +- In Consumer mode, default handling is introduced for Status SubTLV + + +********************** +1.2.1 release (hotfix) +********************** + +Minor +===== +- Fix the ptpd termination in provider mode + + +************* +1.2.0 release +************* + +New Features +============ +- Introduce message compliance for IEEE 802.1 AS & AUTOSAR extensions +- Introduce CRC package dependency into ptpd & sanity check of CRC APIs + +Defect Fix +========== +- Enable conditional generation of configuration header based on underlying SDK and supported features (AZB 68716) + + +************** +R22.11 release +************** + +Defect Fix +========== +- The population of Seconds, SecondsHi and Nanoseconds fields into PTP frame's PreciseOriginTime field, had alignment issues. + Due to this, the time interpreted by the Consumer was incorrect. This issue is now fixed. + +Minor +===== +- Cleanup with respect to API names and variable names + + +************** +R22.08 release +************** + +New Features +============ +- PTPd can now be executed in the Provider mode +- SubTLV support (Userdata) is now introduced. It is available for both Provider and Consumer modes + +Minor +===== +- To distinguish modified PTPd implementation from the original, the binary is now renamed to ``aptpd``, where 'a' stands for 'adapted' diff --git a/src/ptpd/docs/delivery_notes/known_limitations.rst b/src/ptpd/docs/delivery_notes/known_limitations.rst new file mode 100644 index 0000000..5f3b71b --- /dev/null +++ b/src/ptpd/docs/delivery_notes/known_limitations.rst @@ -0,0 +1,15 @@ +################# +Known Limitations +################# + +.. _known_lims_instspec: + +Usage of pcap library +===================== + +- S-CORE Time Synchronization functionality requires ``libpcap`` for transmiting messages over raw ethernet. +- Without ``libpcap``, the exchange of messages happens via UDP, on either IPv4 or IPv6, based on the option chosen + while running ``ptpd``. + +.. note:: + **Please note** that the UDP based use-case is currently not tested and its use is solely at the discretion of users. diff --git a/src/ptpd/docs/detailed_design/Detailed_Design_of_PTP_Daemon.rst b/src/ptpd/docs/detailed_design/Detailed_Design_of_PTP_Daemon.rst new file mode 100644 index 0000000..ea1c76e --- /dev/null +++ b/src/ptpd/docs/detailed_design/Detailed_Design_of_PTP_Daemon.rst @@ -0,0 +1,39 @@ +TSync PTP Daemon Types +====================== + +.. doxygenfile:: score_ptp_types.h + :project: score-ptp-daemon + + +TSync PTP Daemon +================ + +.. doxygenfile:: score_ptp_common.h + :project: score-ptp-daemon + + +TSync PTP Daemon Arithmetic +=========================== + +.. doxygenfile:: score_ptp_arith.h + :project: score-ptp-daemon + + +TSync PTP Daemon SubTLVs +======================== + +.. doxygenfile:: score_ptp_subtlv.h + :project: score-ptp-daemon + + +TSync PTP Config +================ + +.. doxygenfile:: score_ptp_config.h + :project: score-ptp-daemon + +TSync PTP Consumer +================== + +.. doxygenfile:: score_ptp_consumer.h + :project: score-ptp-daemon diff --git a/src/ptpd/docs/detailed_design/detailed_design.rst b/src/ptpd/docs/detailed_design/detailed_design.rst new file mode 100644 index 0000000..0b0c0bf --- /dev/null +++ b/src/ptpd/docs/detailed_design/detailed_design.rst @@ -0,0 +1,8 @@ +############### +Detailed Design +############### + +.. toctree:: + :maxdepth: 1 + + Detailed_Design_of_PTP_Daemon.rst diff --git a/src/ptpd/docs/product_doc/product_doc.rst b/src/ptpd/docs/product_doc/product_doc.rst new file mode 100644 index 0000000..86b9d3e --- /dev/null +++ b/src/ptpd/docs/product_doc/product_doc.rst @@ -0,0 +1,311 @@ +##################### +Product Documentation +##################### + +.. contents:: Contents + +************ +Introduction +************ + +The ``score-ptp-daemon`` is the adaptation of the "ptpd" implementation from the Open source community. On system level, +the major difference between ``ptpd`` and ``score-ptp-daemon``, is the way the Time is sourced (in Provider mode) and +used (in Consumer mode). + +- In Consumer mode, the ptpd changes the system clock based on the timestamp received. However, the ``score-ptp-daemon`` + forwards the Timestamps to ``score::time``. +- In Provider mode, the ptpd reads the system time and forwards it over the Ethernet. ``score-ptp-daemon`` fetches the + Timestamp from ``score::time`` and sends it over Ethernet. + +.. important:: + The ``tsync-daemon`` **MUST** be launched before the ``score-ptp-daemon`` is started - as the latter relies on the + former for Adaptive TSYNC configuration being read from flatbuffers & made available in shared memory. + + +************* +Configuration +************* + +- Currently, most of the TSYNC FC parameters are configurable via ECU configuration for AUTOSAR Adaptive TSYNC. Please + refer to the ``tsync-daemon`` documentation for more details. +- The score-ptp-daemon configuration is done via command line parameters, as described below. +- In the context of Providers, the ``GlobalTimeTxPeriod`` (the interval between two sync messages) is obtained from the configuration. + If this value is not specified, a default interval of 1 second will be used. + + .. note:: + - This parameter is specified in ECU configuration as ``syncPeriod`` + +- In the context of Consumers, the configuration parameter `GlobalTimeFollowUpTimeout` is used to ensure the follow-up message is received within the specified time frame. + + .. note:: + - This parameter is specified in ECU configuration as ``followUpTimeoutValue`` + +- In the context of Consumers, the configuration parameter ``globalTimeSequenceCounterJumpWidth`` is used to detect the sequence-counter-jumps. + If a detected jump in any sync message exceeds the configured threshold, the associated PTP messages are rejected. + +Command Line Parameters +======================= + +The score-ptp-daemon needs some command line parameters to be able to launch successfully. Below is an example showing +how the daemon can be launched. + + .. note:: + - This is only an example. There are multiple permutations of these values possible, which are not described here at present. + +``./aptpd2 -i -d --global:foreground=Y -S --ptpengine:transport=ethernet --ptpengine:delay_mechanism=DELAY_DISABLED --ptpengine:disable_bmca=y --score:globaltimepropagationdelay=0.0 L --ptpengine:dot1as=1 --clock:no_adjust=Y`` +where: + +- ``-i `` = Ethernet interface to use +- ``-d `` = domain number +- ``-S/M`` = Slave (S) or Master (M) modes +- ``--global:foreground=Y`` = Run in foreground +- ``--ptpengine:transport=ethernet`` = Ethernet as transport layer, IEEE 802.3 +- ``--ptpengine:delay_mechanism=DELAY_DISABLED`` = delay_mechanism, E2E delay-request-response method between slave + and master. In case of AUTOSAR, the delay is provided statically via ``globaltimepropagationdelay``. +- ``--ptpengine:disable_bmca=Y`` = Disable Best Master Clock Algorithm for AUTOSAR/automotive use-cases +- ``--score:globaltimepropagationdelay=0.0`` = Static global propagation delay value +- ``-L`` = Default place where ptpd runs is ``/var/run``. It tries to acquire a lock. However, currently ``fcntl(fd, F_GETLK, &fl)`` + always returns "Function isnt implemented" even though the locks are set previously using ``fcntl(fd, F_SETLK, &fl)``. + As a workaround, this option ignores the locking. +- ``--ptpengine:dot1as=1`` = Enable TransportSpecific field compatibility with 802.1AS, requires Ethernet transport +- ``--clock:no_adjust=Y`` = Disable the System Clock update when in the Consumer mode. In consumer mode, with every successful reception of + the PTP message, the offset is checked. If any deviation is observed, the Clock is adjusted. If the Clock update is not required, + this option can be used to disable it. + +.. hint:: + ``--delay_mechanism:`` P2P port-based peer delay message mechanism, ``DELAY_DISABLED`` is the default. + + +.. _tsync_tlv_format: + +********************** +TSYNC Packet Structure +********************** + +- The PTP protocol has two types of messages, ``Sync`` and ``Follow-Up``. +- The Provider first sends the ``Sync`` message followed by the ``Follow-Up`` message. + +AUTOSAR Protocol Extensions +=========================== + +- The tables below explain the frame formats of both message types, in the context of the AUTOSAR TSYNC Protocol: + +Sync Message +------------ + + +------------------------------------------------------------------------+ + | **Sync Message [AUTOSAR]** | + +===========================+==================+============+============+ + | **High Nibble** | **Low Nibble** | **Start** | **End** | + | | | **Index** | **Index** | + +---------------------------+------------------+------------+------------+ + | **transportSpecific** | **message-type** | 0 | 0 | + +---------------------------+------------------+------------+------------+ + | **reserved** | **versionPTP** | 1 | 1 | + +---------------------------+------------------+------------+------------+ + | **length of the message** | | 2 | 3 | + +---------------------------+------------------+------------+------------+ + | **domainNumber** | | 4 | 4 | + +---------------------------+------------------+------------+------------+ + | **reserved** | | 5 | 5 | + +---------------------------+------------------+------------+------------+ + | **flags** | | 6 | 7 | + | | | | | + | | | | | + +---------------------------+------------------+------------+------------+ + | **correctionField** | | 8 | 15 | + +---------------------------+------------------+------------+------------+ + | **reserved** | | 16 | 19 | + +---------------------------+------------------+------------+------------+ + | **sourcePortIdentity** | | 20 | 29 | + +---------------------------+------------------+------------+------------+ + | **sequenceId** | | 30 | 31 | + +---------------------------+------------------+------------+------------+ + | **control** | | 32 | 32 | + +---------------------------+------------------+------------+------------+ + | **logMessageInterval** | | 33 | 33 | + +---------------------------+------------------+------------+------------+ + | **reserved** | | 34 | 43 | + +---------------------------+------------------+------------+------------+ + | **Total Bytes** | 44 | + +---------------------------+------------------+------------+------------+ + +Follow-Up Message +----------------- + + +-------------------------------------------------------------------------------------------------------+ + | **Follow-Up Message Header [AUTOSAR]** | + +================================+==================+======================================+============+ + | **High Nibble** | **Low Nibble** | **Start** | **End** | + | | | **Index** | **Index** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **transportSpecific** | **message-type** | 1 | 0 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **reserved** | **versionPTP** | 1 | 1 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **length of the message** | | 2 | 3 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **domainNumber** | | 4 | 4 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **reserved** | | 5 | 5 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **flags** | | 6 | 7 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **correctionField** | | 8 | 15 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **reserved** | | 16 | 19 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **sourcePortIdentity** | | 20 | 29 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **sequenceId** | | 30 | 31 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **control** | | 32 | 32 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **logMessageInterval** | | 33 | 33 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **preciseOriginTimestamp** | | 34 | 43 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Follow-Up information TLV [IEEE 802.1AS]** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **tlvType** | | 44 | 45 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **length of the field** | | 46 | 47 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **organizationId** | | 48 | 50 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **organizationSubType** | | 51 | 53 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **cumulativeScaledRateOffset** | | 54 | 57 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **gmTimeBaseIndicator** | | 58 | 59 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **lastGmPhaseChange** | | 60 | 71 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **scaledLastGmFreqChange** | | 72 | 75 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Follow-Up information TLV [AUTOSAR]** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **tlvType** | | 76 | 77 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **length of the field** | | 78 | 79 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **organizationId** | | 80 | 82 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **organizationSubType** | | 83 | 85 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: Time Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 86 | 86 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 87 | 87 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **CRC_Time_Flags** | | 88 | 88 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **CRC_Time_0** | | 89 | 89 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **CRC_Time_1** | | 90 | 90 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: Status Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 91 | 91 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 92 | 92 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Status** | | 93 | 93 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **CRC_Status** | | 94 | 94 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: Status Not Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 91 | 91 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 92 | 92 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Status** | | 93 | 93 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **reserved** | | 94 | 94 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: UserData Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 95 | 95 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 96 | 96 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserDataLength** | | 97 | 97 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_0** | | 98 | 98 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_1** | | 99 | 99 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_2** | | 100 | 100 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **CRC_UserData** | | 101 | 101 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: UserData Not Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 95 | 95 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 96 | 96 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserDataLength** | | 97 | 97 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_0** | | 98 | 98 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_1** | | 99 | 99 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_2** | | 100 | 100 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **reserved** | | 101 | 101 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: OFS Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 102 | 102 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 103 | 103 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **OfsTimeDomain** | | 104 | 104 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **OfsTimeSec** | | 105 | 110 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **OfsTimeNSec** | | 111 | 114 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Status** | | 115 | 115 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserDataLength** | | 116 | 116 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_0** | | 117 | 117 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_1** | | 118 | 118 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_2** | | 119 | 119 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **CRC_OFS** | | 120 | 120 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **AUTOSAR TLV Sub-TLV: OFS Not Secured** | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Type** | | 102 | 102 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Length** | | 103 | 103 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **OfsTimeDomain** | | 104 | 104 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **OfsTimeSec** | | 105 | 110 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **OfsTimeNSec** | | 111 | 114 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Status** | | 115 | 115 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserDataLength** | | 116 | 116 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_0** | | 117 | 117 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_1** | | 118 | 118 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **UserByte_2** | | 119 | 119 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **reserved** | | 120 | 120 | + +--------------------------------+------------------+--------------------------------------+------------+ + | **Total Bytes** | 121 | + +------------------------------------------------------------------------------------------+------------+ + +.. TODO: Please add the same tables for purely IEEE 802.1AS messages, without AUTOSAR extensions. diff --git a/src/ptpd/index.rst b/src/ptpd/index.rst new file mode 100644 index 0000000..69e157e --- /dev/null +++ b/src/ptpd/index.rst @@ -0,0 +1,32 @@ +################ +TSync PTP Daemon +################ + +********** +DISCLAIMER +********** + +.. note:: + This score-ptp-daemon module is built upon the Open Source `ptpd implementation `_. + Please refer to the `COPYRIGHT `_ for additional information on copyright and licensing. + For this reason, the documentation is provided only for the AUTOSAR extensions implemented above it. + +.. toctree:: + :maxdepth: 1 + :caption: Documentation + + docs/delivery_notes/delivery_notes.rst + docs/delivery_notes/known_limitations.rst + docs/product_doc/product_doc.rst + docs/delivery_notes/copyright.rst + + +.. toctree:: + :maxdepth: 1 + :caption: Engineering Artifacts + + docs/requirements/requirements.rst + docs/architecture/architecture.rst + docs/detailed_design/detailed_design.rst + tests/tests.rst + diff --git a/src/ptpd/misra-c.dax b/src/ptpd/misra-c.dax new file mode 100644 index 0000000..d0c2a1c --- /dev/null +++ b/src/ptpd/misra-c.dax @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ptpd/packagebuild/rpm-rh/README.RH b/src/ptpd/packagebuild/rpm-rh/README.RH new file mode 100644 index 0000000..ca29ae3 --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/README.RH @@ -0,0 +1,44 @@ +====================== Building PTPd RPMs =================== + +- The rpmbuild.sh included in this directory should perform + a full RPM build for RHEL systems (and CentOS and probably FC). + This was used to build the RPMs starting with 2.3.0 RC2. + The script was successfully used to build under RHEL 5.5, + 5.8, 6.2, 6.5, 7.0 but should build on any RHEL5+ system, CentOS, + Fedora and other distributions following the RH package + naming (-devel suffixes etc.), using systemd when available, + otherwise using system V init scripts. The spec file is easy + to modify to suit your distribution. The rpmbuild.sh script + by default builds both the full build and the slave-only build. + To build a slave-only build, the macro build_slaveonly should be set + to 1 (as in rpmbuild --define "build_slaveonly 1"). + The ptpd package is the full PTP implementation, + whereas the ptpd-slaveonly is built with --enable-slave-only, + so is a version incapable of running as PTP master. + +- The rpmbuild.sh script calls "make dist" to generate + the tarball used for building the RPMs. If you are + building from svn, make sure you run autotools + (autoreconf) before trying to build RPMs. Follow the + README.repocheckout file in the main source directory + if in doubt. + +- rpmbuild.sh will create a temporary build directory, + build to it, move the rpm files to the current directory + and finally clean up by removing the build directory. + What is left is the RPMs in current directory and + nothing else. + +- The number of dependencies can be minimised mainly by + building ptpd without SNMP support (--disable-snmp). + +******************************************************* + +Wojciech Owczarek +2013-2015, PTPd project, http://ptpd.sf.net + +Please report init script and / or spec file +bugs and modifications to the ptpd-devel mailing list +on SourceForge, or use the SourceForge ptpd forums. + +******************************************************* diff --git a/src/ptpd/packagebuild/rpm-rh/ptpd.conf b/src/ptpd/packagebuild/rpm-rh/ptpd.conf new file mode 100644 index 0000000..39297e6 --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/ptpd.conf @@ -0,0 +1,62 @@ +; ============================================================================== +; This is a recommended configuration for a PTPv2 slave +; For a full list of options run ptpd2 -H or see the documentation and man pages +; ============================================================================== + +; interface has to be specified +ptpengine:interface= + +; PTP domain +ptpengine:domain=0 + +; available presets are slaveonly, masteronly and masterslave (full IEEE 1588 implementation) +ptpengine:preset=slaveonly + +; multicast for both sync and delay requests - use hybrid for unicast delay requests +ptpengine:ip_mode=multicast + +; when enabled, instead of sockets, libpcap is used to receive (sniff) and send (inject) packets. +; on low latency hardware such as 10GE NICs this can provide results close to hardware-assisted PTP +ptpengine:use_libpcap=n + +; go into panic mode for 10 minutes instead of resetting the clock +ptpengine:panic_mode=y +ptpengine:panic_mode_duration=10 + +; uncomment this to enable outlier filters +ptpengine:sync_outlier_filter_enable=y +ptpengine:delay_outlier_filter_enable=y + +; store observed drift in a file +clock:drift_handling=file + +; update online statistics every 5 seconds +global:statistics_update_interval=30 + +; wait 5 statistics intervals for one-way delay to stabilise +ptpengine:calibration_delay=5 + +; log file, event log only. if timing statistics are needed, see statistics_file +global:log_file=/var/log/ptpd2.log +; log file up to 5M +global:log_file_max_size=5000 +; rotate logs up to 5 files +global:log_file_max_files=5 + +; status file providing an overview of ptpd's operation and statistics +global:log_status=y + +; required if ip_mode is set to hybrid +;ptpengine:log_delayreq_interval=0 + +; uncomment this to log a timing log like in previous ptpd versions +;global:statistics_file=/var/log/ptpd2.stats + +; on multi-core systems it is recommended to bind ptpd to a single core +;global:cpuaffinity_cpucore=0 + +; use DSCP 46 for expedited forwarding over ipv4 networks +ptpengine:ip_dscp=46 + +; always keep a new line in the end + diff --git a/src/ptpd/packagebuild/rpm-rh/ptpd.init b/src/ptpd/packagebuild/rpm-rh/ptpd.init new file mode 100644 index 0000000..6442d71 --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/ptpd.init @@ -0,0 +1,205 @@ +#!/bin/bash + +# +# ptpd: Precision Time protocol server (IEEE 1588) +# +# chkconfig: 2345 59 73 +# +# description: The PTP daemon (PTPd) implements the Precision Time +# protocol (PTP) as defined by the IEEE 1588 standard. +# PTP was developed to provide very precise time +# coordination of LAN connected computers. + +DAEMON=/usr/sbin/ptpd2 +prog=PTPd +RETVAL=0 +PATH=/sbin:/usr/local/bin:$PATH +DAEMON_DESC="IEEE 1588 Precision Time Protocol (v2) daemon" + +. /etc/init.d/functions + +if test -e /etc/sysconfig/__SERVICENAME__ ; then + + . /etc/sysconfig/__SERVICENAME__ + + PTPD_OPTIONS="--global:lock_file=$PTPD_PID_FILE --global:status_file=$PTPD_STATUS_FILE -c $PTPD_CONFIG_FILE $PTPD_EXTRA_OPTIONS" + +fi + + + +start() { + + status -p $PTPD_PID_FILE $DAEMON > /dev/null 2>&1 + RETVAL=$? + if [ "$RETVAL" -eq "0" ] + then + echo "$prog already running (PID `cat $PTPD_PID_FILE`)." + exit 0 + fi + + echo -n "Starting $DAEMON_DESC..." + + $DAEMON $PTPD_OPTIONS + sleep 1 + status -p $PTPD_PID_FILE $DAEMON > /dev/null 2>&1 + RETVAL=$? + + if [ "$RETVAL" -eq "0" ] + then + echo_success + else + echo_failure + fi + + echo + +} + +checkstatus() { + status -p $PTPD_PID_FILE $DAEMON > /dev/null 2>&1 + RETVAL=$? + if [ "$RETVAL" -eq "0" ] + then + if test -e $PTPD_PID_FILE ; then + echo "$prog is running (PID `cat $PTPD_PID_FILE`)." + fi + else + echo "$prog is not running." + RETVAL=3 + fi + + exit $RETVAL +} + +showinfo() { + status -p $PTPD_PID_FILE $DAEMON > /dev/null 2>&1 + RETVAL=$? + if [ "$RETVAL" -eq "0" ] + then + if test -e $PTPD_PID_FILE ; then + echo "$prog is running (PID `cat $PTPD_PID_FILE`)." + + taskset -pc `cat $PTPD_PID_FILE` + + if test -e $PTPD_STATUS_FILE; then + + cat $PTPD_STATUS_FILE + + fi + + fi + else + echo "$prog is not running." + RETVAL=3 + fi + + exit $RETVAL +} + + +checkconfig() { + + $DAEMON -k $PTPD_OPTIONS + RETVAL=$? +} + +stop() { + status -p $PTPD_PID_FILE $DAEMON > /dev/null 2>&1 + RETVAL=$? + if [ "$RETVAL" -ne "0" ] + then + echo "$prog not running." + else + killproc -p $PTPD_PID_FILE $DAEMON + RETVAL=$? + if [ "$RETVAL" -eq "0" ] + then + echo "Stopping $DAEMON_DESC...`echo_success`" + else + echo "Stopping $DAEMON_DESC...`echo_failure`" + fi + fi +} + +reload() { + + checkconfig + if [ "$RETVAL" -ne "0" ]; then + echo "Reloading $prog: `echo_failure`" + exit $RETVAL + fi + echo -n $"Reloading $prog: " + killproc -p $PTPD_PID_FILE $DAEMON -HUP + RETVAL=$? + echo +} + +help() { + + echo $"Usage: $0 {start|stop|restart|reload|checkconfig|condrestart|status|info}" + exit 1 +} + +if [ -z "$1" ] +then + help +fi + +case "$1" in + start) + start + RETVAL=$? + ;; + + status) + checkstatus + RETVAL=$? + ;; + + info) + showinfo + RETVAL=$? + ;; + + + restart) + stop + sleep 1 + start + RETVAL=$? + ;; + + reload) + reload + RETVAL=$? + + ;; + + stop) + stop + exit 0 + ;; + + checkconfig) + checkconfig + RETVAL=$? + ;; + + + condrestart) + if [ -f PID_FILE ]; then + stop + start + RETVAL=$? + fi + ;; + *) + + echo $"Usage: $0 {start|stop|restart|reload|checkconfig|condrestart|status}" + RETVAL=3 + ;; + +esac + +exit $RETVAL diff --git a/src/ptpd/packagebuild/rpm-rh/ptpd.service b/src/ptpd/packagebuild/rpm-rh/ptpd.service new file mode 100644 index 0000000..9bac32b --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/ptpd.service @@ -0,0 +1,17 @@ +[Unit] +Description=Precision Time Protocol Daemon +After=syslog.target ntpdate.service sntp.service ntp.service chronyd.service network.target + +[Service] +Type=forking +EnvironmentFile=-/etc/sysconfig/__SERVICENAME__ +ExecStart=/usr/sbin/ptpd2 \ + --global:lock_file=${PTPD_PID_FILE} \ + --global:status_file=${PTPD_STATUS_FILE} \ + -c ${PTPD_CONFIG_FILE} \ + ${PTPD_EXTRA_OPTIONS} +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target + diff --git a/src/ptpd/packagebuild/rpm-rh/ptpd.spec b/src/ptpd/packagebuild/rpm-rh/ptpd.spec new file mode 100644 index 0000000..635c43c --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/ptpd.spec @@ -0,0 +1,241 @@ +# (c) 2014-2015: Wojciech Owczarek, PTPd project + +%if %{?build_slaveonly:1}%{!?build_slaveonly:0} +%define slaveonly_build %{build_slaveonly} +%else +%define slaveonly_build 0 +%endif + +%define _use_internal_dependency_generator 0 + +# RHEL5.5 and older don't have the /etc/rpm/macros.dist macros +%if %{?dist:1}%{!?dist:0} +%define distver %{dist} +%else +%define distver %(/usr/lib/rpm/redhat/dist.sh --dist) +%endif + +%define servicename ptpd%{?servicesuffix} + +%if %{slaveonly_build} == 1 +Name: ptpd-slaveonly +Summary: Synchronises system time using the Precision Time Protocol (PTP) implementing the IEEE 1588-2008 (PTP v 2) standard. Slave-only version. +%else +Name: ptpd +Summary: Synchronises system time using the Precision Time Protocol (PTP) implementing the IEEE 1588-2008 (PTP v 2) standard. Full version with master and slave support. +%endif +Version: 2.3.2 +Release: 1%{distver}%{?gittag} +License: distributable +Group: System Environment/Daemons +Vendor: PTPd project team +Source0: ptpd-2.3.2.tar.gz + +Source2: ptpd.sysconfig +Source3: ptpd.conf + +URL: http://ptpd.sf.net + +%if %{slaveonly_build} == 1 +Conflicts: ptpd +%else +Conflicts: ptpd-slaveonly +%endif + +Requires(pre): /sbin/chkconfig +Requires(pre): /bin/awk sed grep + +BuildRequires: libpcap-devel net-snmp-devel openssl-devel zlib-devel redhat-rpm-config +Requires: libpcap net-snmp openssl zlib + +# systemd +%if %{?_unitdir:1}%{!?_unitdir:0} +Source1: ptpd.service +Requires: systemd +BuildRequires: systemd +%else +Source1: ptpd.init +%endif + +BuildRoot: %{_tmppath}/%{name}-root + +%description +The PTP daemon (PTPd) implements the Precision Time +protocol (PTP) as defined by the IEEE 1588 standard. +PTP was developed to provide very precise time +coordination of LAN connected computers. + +Install the ptpd package if you need tools for keeping your system's +time synchronised via the PTP protocol or serving PTP time. + +%prep + +%setup -n ptpd-2.3.2 + +%build + +%if %{slaveonly_build} == 1 +./configure --enable-slave-only --with-max-unicast-destinations=512 +%else +./configure --with-max-unicast-destinations=512 +%endif + +make + +find . -type f | xargs chmod 644 +find . -type d | xargs chmod 755 + +%install +rm -rf $RPM_BUILD_ROOT + +mkdir -p $RPM_BUILD_ROOT%{_mandir}/man{5,8} +mkdir -p $RPM_BUILD_ROOT%{_sbindir} +mkdir -p $RPM_BUILD_ROOT%{_datadir}/ptpd +mkdir -p $RPM_BUILD_ROOT%{_datadir}/snmp/mibs + +install -m 755 src/ptpd2 $RPM_BUILD_ROOT%{_sbindir} +install -m 644 src/ptpd2.8 $RPM_BUILD_ROOT%{_mandir}/man8/ptpd2.8 +install -m 644 src/ptpd2.conf.5 $RPM_BUILD_ROOT%{_mandir}/man5/ptpd2.conf.5 +install -m 644 doc/PTPBASE-MIB.txt $RPM_BUILD_ROOT%{_datadir}/snmp/mibs/PTPBASE-MIB.txt +install -m 644 src/leap-seconds.list $RPM_BUILD_ROOT%{_datadir}/ptpd/leap-seconds.list +install -m 644 src/ptpd2.conf.minimal $RPM_BUILD_ROOT%{_datadir}/ptpd/ptpd2.conf.minimal +install -m 644 src/ptpd2.conf.default-full $RPM_BUILD_ROOT%{_datadir}/ptpd/ptpd2.conf.default-full +install -m 644 src/templates.conf $RPM_BUILD_ROOT%{_datadir}/ptpd/templates.conf + +{ cd $RPM_BUILD_ROOT + +%if %{?_unitdir:1}%{!?_unitdir:0} + mkdir -p .%{_unitdir} + install -m644 $RPM_SOURCE_DIR/ptpd.service .%{_unitdir}/%{servicename}.service + sed -i 's#/etc/sysconfig/__SERVICENAME__#/etc/sysconfig/%{servicename}#' .%{_unitdir}/%{servicename}.service +%else + mkdir -p .%{_initrddir} + install -m755 $RPM_SOURCE_DIR/ptpd.init .%{_initrddir}/%{servicename} + sed -i 's#/etc/sysconfig/__SERVICENAME__#/etc/sysconfig/%{servicename}#' .%{_initrddir}/%{servicename} +%endif + + mkdir -p .%{_sysconfdir}/sysconfig + install -m644 %{SOURCE2} .%{_sysconfdir}/sysconfig/%{servicename} + install -m644 %{SOURCE3} .%{_sysconfdir}/ptpd2.conf + +} + + +%clean +rm -rf $RPM_BUILD_ROOT + +%pre + +%post + +%if %{?_unitdir:1}%{!?_unitdir:0} +/usr/bin/systemctl enable %{servicename} >/dev/null 2>&1 +%else +/sbin/chkconfig --add %{servicename} +%endif +echo +echo -e "**** PTPd - Running post-install checks...\n" + +grep -q : /etc/ethers >/dev/null 2>&1 || echo -e "Consider populating /etc/ethers with the MAC to host mappings of the GMs:\nexample:\t aa:bb:cc:dd:ee:ff gm-1.my.domain.net\n" + +for candidate in ntp chrony; do + +echo -e "\n*** Checking ${candidate}d status...\n" +rpm -q $candidate >/dev/null 2>&1 +if [ $? -ne 0 ]; then + + echo "** ${candidate}d not installed." +else + echo -e "** ${candidate}d is installed.\n" + +%if %{?_unitdir:1}%{!?_unitdir:0} +/usr/bin/systemctl status ${candidate}d | grep Loaded | grep enabled >/dev/null 2>&1 +%else +chkconfig --level `runlevel | awk '{print $2;}'` ${candidate}d >/dev/null 2>&1 +%endif + +if [ $? == 0 ]; then + echo "** ${candidate}d enabled in current runlevel - consider disabling unless:" + echo "- you're running a PTP master with NTP reference" + if [ "$candidate" == "ntp" ]; then + echo -e "- you configure NTP integration in the PTPd configuration file\n"; + fi +fi +%if %{?_unitdir:1}%{!?_unitdir:0} +systemctl status ${candidate}d > /dev/null 2>&1 +%else +service ${candidate}d status > /dev/null 2>&1 +%endif +ret=$? +if [ $ret == 3 ]; then + echo -e "** ${candidate}d not running - good.\n"; +elif [ $ret == 0 ]; then + echo "** ${candidate}d running - consider stopping before running ptpd unless:" + echo "- you're running a PTP master with NTP reference" + if [ "$candidate" == "ntp" ]; then + echo -e "- you configure NTP integration in the PTPd configuration file\n"; + fi + +fi + +fi + +echo + +done + +: + +%preun +if [ $1 = 0 ]; then +%if %{?_unitdir:1}%{!?_unitdir:0} +systemctl stop %{servicename} > /dev/null 2>&1 +systemctl disable %{servicename} >/dev/null 2>&1 +%else + service %{servicename} stop > /dev/null 2>&1 + /sbin/chkconfig --del %{servicename} +%endif +fi +: + +%postun +if [ "$1" -ge "1" ]; then + service %{servicename} condrestart > /dev/null 2>&1 +fi +: + +%files +%defattr(-,root,root) +%{_sbindir}/ptpd2 +%if %{?_unitdir:1}%{!?_unitdir:0} +%{_unitdir}/%{servicename}.service +%else +%config %{_initrddir}/%{servicename} +%endif +%config(noreplace) %{_sysconfdir}/sysconfig/%{servicename} +%config(noreplace) %{_sysconfdir}/ptpd2.conf +%config(noreplace) %{_datadir}/ptpd/templates.conf +%{_mandir}/man8/* +%{_mandir}/man5/* +%{_datadir}/snmp/mibs/* +%{_datadir}/ptpd/* + +%changelog +* Thu Oct 23 2015 Wojciech Owczarek 2.3.2-1 +- version 2.3.2 +* Wed Jul 1 2015 Wojciech Owczarek 2.3.1-2 +- spec updated for OSes with systemd +- chrony detection added to postinstall checks +* Wed Jun 24 2015 Wojciech Owczarek 2.3.1-1 +* Mon Jun 15 2015 Wojciech Owczarek 2.3.1-0.99.rc5 +* Mon Jun 01 2015 Wojciech Owczarek 2.3.1-0.99.rc4 +- rc5 release, adds leap seconds file and config files +* Wed Apr 15 2015 Wojciech Owczarek 2.3.1-0.99.rc4.pre2 +* Mon Apr 13 2015 Wojciech Owczarek 2.3.1-0.99.rc4.pre1 +* Tue Oct 07 2014 Wojciech Owczarek 2.3.1-0.99.rc3 +* Fri Jul 25 2014 Wojciech Owczarek 2.3.1-0.99.rc2 +* Thu Jun 26 2014 Wojciech Owczarek 2.3.1-0.99.rc1 +* Thu Nov 21 2013 Wojciech Owczarek 2.3.0-1 +- Added the PTPBASE SNMP MIB +* Wed Nov 13 2013 Wojciech Owczarek 2.3.0-0.99-rc2 +- Initial public spec file and scripts for RHEL diff --git a/src/ptpd/packagebuild/rpm-rh/ptpd.sysconfig b/src/ptpd/packagebuild/rpm-rh/ptpd.sysconfig new file mode 100644 index 0000000..66de96c --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/ptpd.sysconfig @@ -0,0 +1,10 @@ +# Config file location +PTPD_CONFIG_FILE="/etc/ptpd2.conf" +# Lock / PID file location +PTPD_PID_FILE="/var/run/ptpd2.lock" +# Status file location +PTPD_STATUS_FILE="/var/run/ptpd2.status" + +# Any extra command-line options +PTPD_EXTRA_OPTIONS="" + diff --git a/src/ptpd/packagebuild/rpm-rh/rpmbuild.sh b/src/ptpd/packagebuild/rpm-rh/rpmbuild.sh new file mode 100755 index 0000000..f864612 --- /dev/null +++ b/src/ptpd/packagebuild/rpm-rh/rpmbuild.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +if [ ! -z "$DEBUG" ]; then + set -x +fi + +# ptpd RPM building script +# (c) 2013-2015: Wojciech Owczarek, PTPd project + +while [[ $# -gt 0 ]]; do + opt="$1"; + shift; #expose next argument + case "$opt" in + "--tag" ) + # Add git-based tag + ADD_TAG=1;; + "--service2" ) + # Create ptpd2 service + SERVICESUFFIX='2';; + *) cat >&2 <=3 {ORS=""; print $2}'` + RPMFILE=$BUILDDIR/RPMS/`uname -m`/$ARCHIVE.`uname -m`.rpm + SRPMFILE=$BUILDDIR/SRPMS/$ARCHIVE.src.rpm + + if [ -z "$ADD_TAG" ]; then + TAG_ARG='__gittag notag' # cannot be empty + else + tag=`git log --format=.%ct.%h -1` # based on last commit, timestamp for increasing versions + TAG_ARG="gittag $tag" + fi + + if [ -z "$SERVICESUFFIX" ]; then + SERVICESUFFIX_ARG='__servicesuffix nosuffix' # cannot be empty + else + SERVICESUFFIX_ARG="servicesuffix $SERVICESUFFIX" + fi + + + rpmbuild --define "$TAG_ARG" --define "$SERVICESUFFIX_ARG" --define "build_slaveonly $slaveonly" --define "_topdir $BUILDDIR" -ba $BUILDDIR/SPECS/$SPEC && { + find $BUILDDIR -name "*.rpm" -exec mv {} $PWD \; + } + + rm -rf $BUILDDIR + rm $TARBALL + + echo "Moved rpms to $PWD" +done diff --git a/src/ptpd/spdx/ptpd.spdx.yaml b/src/ptpd/spdx/ptpd.spdx.yaml new file mode 100644 index 0000000..afd3b10 --- /dev/null +++ b/src/ptpd/spdx/ptpd.spdx.yaml @@ -0,0 +1,22 @@ +SPDXID: SPDXRef-DOCUMENT +creationInfo: + created: '2023-12-11T08:57:24Z' + creators: + - 'Organization: Robert Bosch GmbH' +dataLicense: CC0-1.0 +documentNamespace: https://etas.com/spdx-oss-component-3ed1f8fc-c0e2-4958-8515-32245c2d5e48 +name: SPDX Document for OSS component ptpd +packages: +- SPDXID: SPDXRef-PACKAGE-ptpd-deee1469-c0ae-4125-906c-8b072a15fa89 + downloadLocation: NONE + filesAnalyzed: false + homepage: https://github.com/ptpd/ptpd + licenseConcluded: NONE + licenseDeclared: BSD-2-Clause + name: ptpd + versionInfo: 2.3.2 +relationships: +- relatedSpdxElement: SPDXRef-PACKAGE-ptpd-deee1469-c0ae-4125-906c-8b072a15fa89 + relationshipType: DESCRIBES + spdxElementId: SPDXRef-DOCUMENT +spdxVersion: SPDX-2.3 diff --git a/src/ptpd/src/Doxyfile b/src/ptpd/src/Doxyfile new file mode 100644 index 0000000..b5cc7bb --- /dev/null +++ b/src/ptpd/src/Doxyfile @@ -0,0 +1,1511 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = score-ptp-daemon + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = Doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +GENERATE_HTMLHELP = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = YES + diff --git a/src/ptpd/src/arith.c b/src/ptpd/src/arith.c new file mode 100644 index 0000000..4c13d61 --- /dev/null +++ b/src/ptpd/src/arith.c @@ -0,0 +1,373 @@ +/*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file arith.c + * @date Tue Jul 20 16:12:51 2010 + * + * @brief Time format conversion routines and additional math functions. + * + * + */ + +#include "ptpd.h" + +void +internalTime_to_integer64(TimeInternal internal, Integer64 *bigint) +{ + int64_t scaledNanoseconds; + + scaledNanoseconds = internal.seconds; + scaledNanoseconds *= 1000000000; + scaledNanoseconds += internal.nanoseconds; + scaledNanoseconds <<= 16; + + bigint->msb = (scaledNanoseconds >> 32) & 0x00000000ffffffff; + bigint->lsb = scaledNanoseconds & 0x00000000ffffffff; +} + +void +integer64_to_internalTime(Integer64 bigint, TimeInternal * internal) +{ + int sign; + int64_t scaledNanoseconds; + + scaledNanoseconds = bigint.msb; + scaledNanoseconds <<=32; + scaledNanoseconds += bigint.lsb; + + /*determine sign of result big integer number*/ + + if (scaledNanoseconds < 0) { + scaledNanoseconds = -scaledNanoseconds; + sign = -1; + } else { + sign = 1; + } + + /*fractional nanoseconds are excluded (see 5.3.2)*/ + scaledNanoseconds >>= 16; + internal->seconds = sign * (scaledNanoseconds / 1000000000); + internal->nanoseconds = sign * (scaledNanoseconds % 1000000000); +} + + +void +fromInternalTime(const TimeInternal * internal, Timestamp * external) +{ + + /* + * fromInternalTime is only used to convert time given by the system + * to a timestamp. As a consequence, no negative value can normally + * be found in (internal) + * + * Note that offsets are also represented with TimeInternal structure, + * and can be negative, but offset are never convert into Timestamp + * so there is no problem here. + */ + + if ((internal->seconds & ~INT_MAX) || + (internal->nanoseconds & ~INT_MAX)) { + DBG("Negative value canno't be converted into timestamp \n"); + return; + } else { + external->secondsField.lsb = internal->seconds; + external->nanosecondsField = internal->nanoseconds; + external->secondsField.msb = 0; + } +} + +void +toInternalTime(TimeInternal * internal, const Timestamp * external) +{ + + /* Program will not run after 2038... */ + if (external->secondsField.lsb < INT_MAX) { + internal->seconds = external->secondsField.lsb; + internal->nanoseconds = external->nanosecondsField; + } else { + DBG("Clock servo canno't be executed : " + "seconds field is higher than signed integer (32bits) \n"); + return; + } +} + +void +ts_to_InternalTime(const struct timespec *a, TimeInternal * b) +{ + + b->seconds = a->tv_sec; + b->nanoseconds = a->tv_nsec; +} + +void +tv_to_InternalTime(const struct timeval *a, TimeInternal * b) +{ + + b->seconds = a->tv_sec; + b->nanoseconds = a->tv_usec * 1000; +} + + +void +normalizeTime(TimeInternal * r) +{ + r->seconds += r->nanoseconds / 1000000000; + r->nanoseconds -= (r->nanoseconds / 1000000000) * 1000000000; + + if (r->seconds > 0 && r->nanoseconds < 0) { + r->seconds -= 1; + r->nanoseconds += 1000000000; + } else if (r->seconds < 0 && r->nanoseconds > 0) { + r->seconds += 1; + r->nanoseconds -= 1000000000; + } +} + +void +addTime(TimeInternal * r, const TimeInternal * x, const TimeInternal * y) +{ + r->seconds = x->seconds + y->seconds; + r->nanoseconds = x->nanoseconds + y->nanoseconds; + + normalizeTime(r); +} + +void +subTime(TimeInternal * r, const TimeInternal * x, const TimeInternal * y) +{ + r->seconds = x->seconds - y->seconds; + r->nanoseconds = x->nanoseconds - y->nanoseconds; + + normalizeTime(r); +} + +/// Divide an internal time value +/// +/// @param r the time to convert +/// @param divisor +/// + +#if 0 +/* TODO: this function could be simplified, as currently it is only called to halve the time */ +void +divTime(TimeInternal *r, int divisor) +{ + + uint64_t nanoseconds; + + if (divisor <= 0) + return; + + nanoseconds = ((uint64_t)r->seconds * 1000000000) + r->nanoseconds; + nanoseconds /= divisor; + + r->seconds = 0; + r->nanoseconds = nanoseconds; + normalizeTime(r); +} +#endif + +void +div2Time(TimeInternal *r) +{ + r->nanoseconds += (r->seconds % 2) * 1000000000; + r->seconds /= 2; + r->nanoseconds /= 2; + + normalizeTime(r); +} + + + +/* clear an internal time value */ +void +clearTime(TimeInternal *r) +{ + r->seconds = 0; + r->nanoseconds = 0; +} + + +/* sets a time value to a certain nanoseconds */ +void +nano_to_Time(TimeInternal *x, int nano) +{ + x->seconds = 0; + x->nanoseconds = nano; + normalizeTime(x); +} + +/* greater than operation */ +int +gtTime(const TimeInternal *x, const TimeInternal *y) +{ + TimeInternal r; + + subTime(&r, x, y); + return !isTimeInternalNegative(&r); +} + +/* remove sign from variable */ +void +absTime(TimeInternal *r) +{ + /* Make sure signs are the same */ + normalizeTime(r); + r->seconds = abs(r->seconds); + r->nanoseconds = abs(r->nanoseconds); +} + + +/* if 2 time values are close enough for X nanoseconds */ +int +is_Time_close(const TimeInternal *x, const TimeInternal *y, int nanos) +{ + TimeInternal r1; + TimeInternal r2; + + // first, subtract the 2 values. then call abs(), + // then call gtTime for requested the number of nanoseconds + subTime(&r1, x, y); + absTime(&r1); + + nano_to_Time(&r2, nanos); + + return !gtTime(&r1, &r2); +} + + +int +check_timestamp_is_fresh2(const TimeInternal * timeA, const TimeInternal * timeB) +{ + int ret; + + // maximum 1 millisecond offset + ret = is_Time_close(timeA, timeB, 1000000); + DBG2("check_timestamp_is_fresh: %d\n ", ret); + return ret; +} + + +int +check_timestamp_is_fresh(const TimeInternal * timeA) +{ + TimeInternal timeB; + getTime(&timeB); + + return check_timestamp_is_fresh2(timeA, &timeB); +} + + +int +isTimeInternalNegative(const TimeInternal * p) +{ + return (p->seconds < 0) || (p->nanoseconds < 0); +} + +double +secondsToMidnight(void) +{ + TimeInternal now; + double stm, ret; + getTime(&now); + stm = 86400.0 - (now.seconds % 86400); + ret = (stm - now.nanoseconds / 1E9); + return ret; +} + +double +getPauseAfterMidnight(Integer8 announceInterval, int pausePeriod) +{ + double ai = pow(2,announceInterval); + + if (pausePeriod > 2.0 * ai) + return (pausePeriod); + else + return (2.0 * ai); +} + +double +timeInternalToDouble(const TimeInternal * p) +{ + + double sign = (p->seconds < 0 || p->nanoseconds < 0 ) ? -1.0 : 1.0; + return (sign * ( abs(p->seconds) + abs(p->nanoseconds) / 1E9 )); + +} + +TimeInternal +doubleToTimeInternal(const double d) +{ + + TimeInternal t = {0, 0}; + + t.seconds = trunc(d); + t.nanoseconds = (d - (t.seconds + 0.0)) * 1E9; + + return t; + +} + +/* FNV-1 hash, 32-bit, optional modulo limiter */ +uint32_t +fnvHash(void *input, size_t len, int modulo) +{ + + int i = 0; + + static uint32_t prime = 16777619; + static uint32_t basis = 2166136261; + + uint32_t hash = basis; + uint8_t *buf = (uint8_t*)input; + + for(i = 0; i < len; i++) { + hash *= prime; + hash ^= *(buf + i); + } + + return (modulo > 0 ? hash % modulo : hash); + +} diff --git a/src/ptpd/src/bmc.c b/src/ptpd/src/bmc.c new file mode 100644 index 0000000..b5249ab --- /dev/null +++ b/src/ptpd/src/bmc.c @@ -0,0 +1,779 @@ +/*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file bmc.c + * @date Wed Jun 23 09:36:09 2010 + * + * @brief Best master clock selection code. + * + * The functions in this file are used by the daemon to select the + * best master clock from any number of possibilities. + */ + +#include "ptpd.h" + + +/* Init ptpClock with run time values (initialization constants are in constants.h)*/ +void initData(RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + int i,j; + j=0; + DBG("initData\n"); + + /* Default data set */ + ptpClock->defaultDS.twoStepFlag = TWO_STEP_FLAG; + + /* + * init clockIdentity with MAC address and 0xFF and 0xFE. see + * spec 7.5.2.2.2 + */ + for (i=0;idefaultDS.clockIdentity[i]=0xFF; + else if (i==4) ptpClock->defaultDS.clockIdentity[i]=0xFE; + else + { + ptpClock->defaultDS.clockIdentity[i]=ptpClock->netPath.interfaceID[j]; + j++; + } + } + + ptpClock->portDS.lastMismatchedDomain = -1; + + if(rtOpts->pidAsClockId) { + uint16_t pid = htons(getpid()); + memcpy(ptpClock->defaultDS.clockIdentity + 3, &pid, 2); + } + + ptpClock->bestMaster = NULL; + ptpClock->defaultDS.numberPorts = NUMBER_PORTS; + + ptpClock->disabled = rtOpts->portDisabled; + + memset(ptpClock->userDescription, 0, sizeof(ptpClock->userDescription)); + memcpy(ptpClock->userDescription, rtOpts->portDescription, strlen(rtOpts->portDescription)); + + memset(&ptpClock->profileIdentity,0,6); + + if(rtOpts->ipMode == IPMODE_UNICAST && rtOpts->unicastNegotiation) { + memcpy(&ptpClock->profileIdentity, &PROFILE_ID_TELECOM,6); + } + + if(rtOpts->ipMode == IPMODE_MULTICAST &&rtOpts->delayMechanism == E2E) { + memcpy(&ptpClock->profileIdentity, &PROFILE_ID_DEFAULT_E2E,6); + } + + if(rtOpts->ipMode == IPMODE_MULTICAST &&rtOpts->delayMechanism == P2P) { + memcpy(&ptpClock->profileIdentity, &PROFILE_ID_DEFAULT_P2P,6); + } + + if(rtOpts->dot1AS) { + memcpy(&ptpClock->profileIdentity, &PROFILE_ID_802_1AS,6); + } + + ptpClock->defaultDS.clockQuality.clockAccuracy = + rtOpts->clockQuality.clockAccuracy; + ptpClock->defaultDS.clockQuality.clockClass = rtOpts->clockQuality.clockClass; + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance = + rtOpts->clockQuality.offsetScaledLogVariance; + + ptpClock->defaultDS.priority1 = rtOpts->priority1; + ptpClock->defaultDS.priority2 = rtOpts->priority2; + + ptpClock->defaultDS.domainNumber = rtOpts->domainNumber; + + if(rtOpts->slaveOnly) { + ptpClock->defaultDS.slaveOnly = TRUE; + rtOpts->clockQuality.clockClass = SLAVE_ONLY_CLOCK_CLASS; + ptpClock->defaultDS.clockQuality.clockClass = SLAVE_ONLY_CLOCK_CLASS; + } + +/* Port configuration data set */ + + /* + * PortIdentity Init (portNumber = 1 for an ardinary clock spec + * 7.5.2.3) + */ + copyClockIdentity(ptpClock->portDS.portIdentity.clockIdentity, + ptpClock->defaultDS.clockIdentity); + ptpClock->portDS.portIdentity.portNumber = rtOpts->portNumber; + + /* select the initial rate of delayreqs until we receive the first announce message */ + + ptpClock->portDS.logMinDelayReqInterval = rtOpts->initial_delayreq; + + clearTime(&ptpClock->portDS.peerMeanPathDelay); + + ptpClock->portDS.logAnnounceInterval = rtOpts->logAnnounceInterval; + ptpClock->portDS.announceReceiptTimeout = rtOpts->announceReceiptTimeout; + ptpClock->portDS.logSyncInterval = rtOpts->logSyncInterval; + ptpClock->portDS.delayMechanism = rtOpts->delayMechanism; + ptpClock->portDS.logMinPdelayReqInterval = rtOpts->logMinPdelayReqInterval; + ptpClock->portDS.versionNumber = VERSION_PTP; + + if(rtOpts->dot1AS) { + ptpClock->portDS.transportSpecific = TSP_ETHERNET_AVB; + } else { + ptpClock->portDS.transportSpecific = TSP_DEFAULT; + } + + /* + * Initialize random number generator using same method as ptpv1: + * seed is now initialized from the last bytes of our mac addres (collected in net.c:findIface()) + */ + srand((ptpClock->netPath.interfaceID[PTP_UUID_LENGTH - 1] << 8) + + ptpClock->netPath.interfaceID[PTP_UUID_LENGTH - 2]); + + /*Init other stuff*/ + ptpClock->number_foreign_records = 0; + ptpClock->max_foreign_records = rtOpts->max_foreign_records; +} + +/* memcmp behaviour: -1: ab, 0: a=b */ +int +cmpPortIdentity(const PortIdentity *a, const PortIdentity *b) +{ + + int comp; + + /* compare clock identity first */ + + comp = memcmp(a->clockIdentity, b->clockIdentity, CLOCK_IDENTITY_LENGTH); + + if(comp !=0) { + return comp; + } + + /* then compare portNumber */ + + if(a->portNumber > b->portNumber) { + return 1; + } + + if(a->portNumber < b->portNumber) { + return -1; + } + + return 0; + +} + +/* compare portIdentity to an empty one */ +Boolean portIdentityEmpty(PortIdentity *portIdentity) { + + PortIdentity zero; + memset(&zero, 0, sizeof(PortIdentity)); + + if (!cmpPortIdentity(portIdentity, &zero)) { + return TRUE; + } + + return FALSE; +} + +/* compare portIdentity to all ones port identity */ +Boolean portIdentityAllOnes(PortIdentity *portIdentity) { + + PortIdentity allOnes; + memset(&allOnes, 0xFF, sizeof(PortIdentity)); + + if (!cmpPortIdentity(portIdentity, &allOnes)) { + return TRUE; + } + + return FALSE; +} + +/*Local clock is becoming Master. Table 13 (9.3.5) of the spec.*/ +void m1(const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + /*Current data set update*/ + ptpClock->currentDS.stepsRemoved = 0; + + clearTime(&ptpClock->currentDS.offsetFromMaster); + clearTime(&ptpClock->currentDS.meanPathDelay); + + copyClockIdentity(ptpClock->parentDS.parentPortIdentity.clockIdentity, + ptpClock->defaultDS.clockIdentity); + + ptpClock->parentDS.parentPortIdentity.portNumber = ptpClock->portDS.portIdentity.portNumber; + ptpClock->parentDS.parentStats = DEFAULT_PARENTS_STATS; + ptpClock->parentDS.observedParentClockPhaseChangeRate = 0; + ptpClock->parentDS.observedParentOffsetScaledLogVariance = 0; + copyClockIdentity(ptpClock->parentDS.grandmasterIdentity, + ptpClock->defaultDS.clockIdentity); + ptpClock->parentDS.grandmasterClockQuality.clockAccuracy = + ptpClock->defaultDS.clockQuality.clockAccuracy; + ptpClock->parentDS.grandmasterClockQuality.clockClass = + ptpClock->defaultDS.clockQuality.clockClass; + ptpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance = + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance; + ptpClock->parentDS.grandmasterPriority1 = ptpClock->defaultDS.priority1; + ptpClock->parentDS.grandmasterPriority2 = ptpClock->defaultDS.priority2; + ptpClock->portDS.logMinDelayReqInterval = rtOpts->logMinDelayReqInterval; + + /*Time Properties data set*/ + ptpClock->timePropertiesDS.currentUtcOffsetValid = rtOpts->timeProperties.currentUtcOffsetValid; + ptpClock->timePropertiesDS.currentUtcOffset = rtOpts->timeProperties.currentUtcOffset; + ptpClock->timePropertiesDS.timeTraceable = rtOpts->timeProperties.timeTraceable; + ptpClock->timePropertiesDS.frequencyTraceable = rtOpts->timeProperties.frequencyTraceable; + ptpClock->timePropertiesDS.ptpTimescale = rtOpts->timeProperties.ptpTimescale; + ptpClock->timePropertiesDS.timeSource = rtOpts->timeProperties.timeSource; + + if(ptpClock->timePropertiesDS.ptpTimescale && + (secondsToMidnight() < rtOpts->leapSecondNoticePeriod)) { + ptpClock->timePropertiesDS.leap59 = ptpClock->clockStatus.leapDelete; + ptpClock->timePropertiesDS.leap61 = ptpClock->clockStatus.leapInsert; + } else { + ptpClock->timePropertiesDS.leap59 = FALSE; + ptpClock->timePropertiesDS.leap61 = FALSE; + } + +} + + +/* first cut on a passive mode specific BMC actions */ +void p1(PtpClock *ptpClock, const RunTimeOpts *rtOpts) +{ + /* make sure we revert to ARB timescale in Passive mode*/ + if(ptpClock->portDS.portState == PTP_PASSIVE){ + ptpClock->timePropertiesDS.currentUtcOffsetValid = rtOpts->timeProperties.currentUtcOffsetValid; + ptpClock->timePropertiesDS.currentUtcOffset = rtOpts->timeProperties.currentUtcOffset; + } + +} + + +/*Local clock is synchronized to Ebest Table 16 (9.3.5) of the spec*/ +void s1(MsgHeader *header,MsgAnnounce *announce,PtpClock *ptpClock, const RunTimeOpts *rtOpts) +{ + + Boolean firstUpdate = !cmpPortIdentity(&ptpClock->parentDS.parentPortIdentity, &ptpClock->portDS.portIdentity); + TimePropertiesDS tpPrevious = ptpClock->timePropertiesDS; + + Boolean previousLeap59 = FALSE; + Boolean previousLeap61 = FALSE; + + Boolean leapChange = FALSE; + + Integer16 previousUtcOffset = 0; + + previousLeap59 = ptpClock->timePropertiesDS.leap59; + previousLeap61 = ptpClock->timePropertiesDS.leap61; + + previousUtcOffset = ptpClock->timePropertiesDS.currentUtcOffset; + + /* Current DS */ + ptpClock->currentDS.stepsRemoved = announce->stepsRemoved + 1; + + /* Parent DS */ + copyClockIdentity(ptpClock->parentDS.parentPortIdentity.clockIdentity, + header->sourcePortIdentity.clockIdentity); + ptpClock->parentDS.parentPortIdentity.portNumber = + header->sourcePortIdentity.portNumber; + copyClockIdentity(ptpClock->parentDS.grandmasterIdentity, + announce->grandmasterIdentity); + ptpClock->parentDS.grandmasterClockQuality.clockAccuracy = + announce->grandmasterClockQuality.clockAccuracy; + ptpClock->parentDS.grandmasterClockQuality.clockClass = + announce->grandmasterClockQuality.clockClass; + ptpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance = + announce->grandmasterClockQuality.offsetScaledLogVariance; + ptpClock->parentDS.grandmasterPriority1 = announce->grandmasterPriority1; + ptpClock->parentDS.grandmasterPriority2 = announce->grandmasterPriority2; + + /* use the granted interval if using signaling, otherwise we would try to arm a timer for 2^127! */ + if(rtOpts->unicastNegotiation && ptpClock->parentGrants != NULL && ptpClock->parentGrants->grantData[ANNOUNCE_INDEXED].granted) { + ptpClock->portDS.logAnnounceInterval = ptpClock->parentGrants->grantData[ANNOUNCE_INDEXED].logInterval; + } else if (header->logMessageInterval != UNICAST_MESSAGEINTERVAL) { + ptpClock->portDS.logAnnounceInterval = header->logMessageInterval; + } + + /* Timeproperties DS */ + ptpClock->timePropertiesDS.currentUtcOffset = announce->currentUtcOffset; + + if (ptpClock->portDS.portState != PTP_PASSIVE && ptpClock->timePropertiesDS.currentUtcOffsetValid && + !IS_SET(header->flagField1, UTCV)) { + if(rtOpts->alwaysRespectUtcOffset) + WARNING("UTC Offset no longer valid and ptpengine:always_respect_utc_offset is set: continuing as normal\n"); + else + WARNING("UTC Offset no longer valid - clock jump expected\n"); + } + + /* "Valid" is bit 2 in second octet of flagfield */ + ptpClock->timePropertiesDS.currentUtcOffsetValid = IS_SET(header->flagField1, UTCV); + + /* set PTP_PASSIVE-specific state */ + p1(ptpClock, rtOpts); + + /* only set leap flags in slave state - info from leap file takes priority*/ + if (ptpClock->portDS.portState == PTP_SLAVE) { + if(ptpClock->clockStatus.override) { + ptpClock->timePropertiesDS.currentUtcOffset = ptpClock->clockStatus.utcOffset; + ptpClock->timePropertiesDS.leap59 = ptpClock->clockStatus.leapDelete; + ptpClock->timePropertiesDS.leap61 = ptpClock->clockStatus.leapInsert; + } else { + ptpClock->timePropertiesDS.leap59 = IS_SET(header->flagField1, LI59); + ptpClock->timePropertiesDS.leap61 = IS_SET(header->flagField1, LI61); + } + } + + ptpClock->timePropertiesDS.timeTraceable = IS_SET(header->flagField1, TTRA); + ptpClock->timePropertiesDS.frequencyTraceable = IS_SET(header->flagField1, FTRA); + ptpClock->timePropertiesDS.ptpTimescale = IS_SET(header->flagField1, PTPT); + ptpClock->timePropertiesDS.timeSource = announce->timeSource; + + /* if Announce was accepted from some domain, so be it */ + if(rtOpts->anyDomain || rtOpts->unicastNegotiation) { + ptpClock->defaultDS.domainNumber = header->domainNumber; + } + + /* + * tell the clock source to update TAI offset, but only if timescale is + * PTP not ARB - spec section 7.2 + */ + if (ptpClock->timePropertiesDS.ptpTimescale && + (ptpClock->timePropertiesDS.currentUtcOffsetValid || rtOpts->alwaysRespectUtcOffset) && + (ptpClock->timePropertiesDS.currentUtcOffset != previousUtcOffset)) { + INFO("UTC offset is now %d\n", + ptpClock->timePropertiesDS.currentUtcOffset); + ptpClock->clockStatus.utcOffset = ptpClock->timePropertiesDS.currentUtcOffset; + ptpClock->clockStatus.update = TRUE; + + } + + if(!firstUpdate && memcmp(&tpPrevious,&ptpClock->timePropertiesDS, sizeof(TimePropertiesDS))) { + /* this is an event - will be picked up and dispatched, no need to set false */ + SET_ALARM(ALRM_TIMEPROP_CHANGE, TRUE); + } + + /* non-slave logic done, exit if not slave */ + if (ptpClock->portDS.portState != PTP_SLAVE) { + return; + } + + /* Leap second handling */ + + if(ptpClock->timePropertiesDS.leap59 && ptpClock->timePropertiesDS.leap61) { + DBG("Both Leap59 and Leap61 flags set!\n"); + ptpClock->counters.protocolErrors++; + return; + } + + /* One of the flags has been cleared */ + leapChange = ((previousLeap59 && !ptpClock->timePropertiesDS.leap59) || + (previousLeap61 && !ptpClock->timePropertiesDS.leap61)); + + if(ptpClock->leapSecondPending && leapChange && !ptpClock->leapSecondInProgress) { + WARNING("Leap second event aborted by GM!\n"); + ptpClock->leapSecondPending = FALSE; + ptpClock->leapSecondInProgress = FALSE; + timerStop(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER]); + ptpClock->clockStatus.leapInsert = FALSE; + ptpClock->clockStatus.leapDelete = FALSE; + ptpClock->clockStatus.update = TRUE; + } + + /* + * one of the leap second flags is lit + * but we have no event pending + */ + + /* changes in flags while not waiting for leap second */ + + if(!ptpClock->leapSecondPending && !ptpClock->leapSecondInProgress) { + + if(rtOpts->leapSecondHandling == LEAP_ACCEPT) { + ptpClock->clockStatus.leapInsert = ptpClock->timePropertiesDS.leap61; + ptpClock->clockStatus.leapDelete = ptpClock->timePropertiesDS.leap59; + } + + if(ptpClock->timePropertiesDS.leap61 || ptpClock->timePropertiesDS.leap59) { + ptpClock->clockStatus.update = TRUE; + /* only set the flag, the rest happens in doState() */ + ptpClock->leapSecondPending = TRUE; + } + } + + /* UTC offset has changed */ + if(previousUtcOffset != ptpClock->timePropertiesDS.currentUtcOffset) { + if(!ptpClock->leapSecondPending && !ptpClock->leapSecondInProgress) { + WARNING("UTC offset changed from %d to %d with " + "no leap second pending!\n", + previousUtcOffset, + ptpClock->timePropertiesDS.currentUtcOffset); + } else { + WARNING("UTC offset changed from %d to %d\n", + previousUtcOffset,ptpClock->timePropertiesDS.currentUtcOffset); + } + } +} + + +/*Copy local data set into header and announce message. 9.3.4 table 12*/ +static void +copyD0(MsgHeader *header, MsgAnnounce *announce, PtpClock *ptpClock) +{ + announce->grandmasterPriority1 = ptpClock->defaultDS.priority1; + copyClockIdentity(announce->grandmasterIdentity, + ptpClock->defaultDS.clockIdentity); + announce->grandmasterClockQuality.clockClass = + ptpClock->defaultDS.clockQuality.clockClass; + announce->grandmasterClockQuality.clockAccuracy = + ptpClock->defaultDS.clockQuality.clockAccuracy; + announce->grandmasterClockQuality.offsetScaledLogVariance = + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance; + announce->grandmasterPriority2 = ptpClock->defaultDS.priority2; + announce->stepsRemoved = 0; + copyClockIdentity(header->sourcePortIdentity.clockIdentity, + ptpClock->defaultDS.clockIdentity); + + /* Copy TimePropertiesDS into FlagField1 */ + header->flagField1 = ptpClock->timePropertiesDS.leap61 << 0; + header->flagField1 |= ptpClock->timePropertiesDS.leap59 << 1; + header->flagField1 |= ptpClock->timePropertiesDS.currentUtcOffsetValid << 2; + header->flagField1 |= ptpClock->timePropertiesDS.ptpTimescale << 3; + header->flagField1 |= ptpClock->timePropertiesDS.timeTraceable << 4; + header->flagField1 |= ptpClock->timePropertiesDS.frequencyTraceable << 5; + +} + + +/*Data set comparison bewteen two foreign masters (9.3.4 fig 27) + * return similar to memcmp() */ + +static Integer8 +bmcDataSetComparison(const ForeignMasterRecord *a, const ForeignMasterRecord *b, const PtpClock *ptpClock, const RunTimeOpts *rtOpts) +{ + + + DBGV("Data set comparison \n"); + short comp = 0; + + + /* disqualification comes above anything else */ + + if(a->disqualified > b->disqualified) { + return -1; + } + + if(a->disqualified < b->disqualified) { + return 1; + } + + /*Identity comparison*/ + comp = memcmp(a->announce.grandmasterIdentity,b->announce.grandmasterIdentity,CLOCK_IDENTITY_LENGTH); + + if (comp!=0) + goto dataset_comp_part_1; + + /* Algorithm part2 Fig 28 */ + if (a->announce.stepsRemoved > b->announce.stepsRemoved+1) + return 1; + if (a->announce.stepsRemoved+1 < b->announce.stepsRemoved) + return -1; + + /* A within 1 of B */ + + if (a->announce.stepsRemoved > b->announce.stepsRemoved) { + comp = memcmp(a->header.sourcePortIdentity.clockIdentity,ptpClock->parentDS.parentPortIdentity.clockIdentity,CLOCK_IDENTITY_LENGTH); + if(comp < 0) + return -1; + if(comp > 0) + return 1; + DBG("Sender=Receiver : Error -1"); + return 0; + } + + if (a->announce.stepsRemoved < b->announce.stepsRemoved) { + comp = memcmp(b->header.sourcePortIdentity.clockIdentity,ptpClock->parentDS.parentPortIdentity.clockIdentity,CLOCK_IDENTITY_LENGTH); + + if(comp < 0) + return -1; + if(comp > 0) + return 1; + DBG("Sender=Receiver : Error -1"); + return 0; + } + /* steps removed A = steps removed B */ + comp = memcmp(a->header.sourcePortIdentity.clockIdentity,b->header.sourcePortIdentity.clockIdentity,CLOCK_IDENTITY_LENGTH); + + if (comp<0) { + return -1; + } + + if (comp>0) { + return 1; + } + + /* identity A = identity B */ + + if (a->header.sourcePortIdentity.portNumber < b->header.sourcePortIdentity.portNumber) + return -1; + if (a->header.sourcePortIdentity.portNumber > b->header.sourcePortIdentity.portNumber) + return 1; + + DBG("Sender=Receiver : Error -2"); + return 0; + + /* Algorithm part 1 Fig 27 */ +dataset_comp_part_1: + + /* OPTIONAL domain comparison / any domain */ + + if(rtOpts->anyDomain) { + /* part 1: preferred domain wins */ + if(a->header.domainNumber == rtOpts->domainNumber && b->header.domainNumber != ptpClock->defaultDS.domainNumber) + return -1; + if(a->header.domainNumber != rtOpts->domainNumber && b->header.domainNumber == ptpClock->defaultDS.domainNumber) + return 1; + + /* part 2: lower domain wins */ + if(a->header.domainNumber < b->header.domainNumber) + return -1; + + if(a->header.domainNumber > b->header.domainNumber) + return 1; + } + + /* Compare localPreference - only used by slaves when using unicast negotiation */ + DBGV("bmcDataSetComparison a->localPreference: %d, b->localPreference: %d\n", a->localPreference, b->localPreference); + if(a->localPreference < b->localPreference) { + return -1; + } + if(a->localPreference > b->localPreference) { + return 1; + } + + /* Compare GM priority1 */ + if (a->announce.grandmasterPriority1 < b->announce.grandmasterPriority1) + return -1; + if (a->announce.grandmasterPriority1 > b->announce.grandmasterPriority1) + return 1; + + /* non-standard BMC extension to prioritise GMs with UTC valid */ + if(rtOpts->preferUtcValid) { + Boolean utcA = IS_SET(a->header.flagField1, UTCV); + Boolean utcB = IS_SET(b->header.flagField1, UTCV); + if(utcA > utcB) + return -1; + if(utcA < utcB) + return 1; + } + + /* Compare GM class */ + if (a->announce.grandmasterClockQuality.clockClass < + b->announce.grandmasterClockQuality.clockClass) + return -1; + if (a->announce.grandmasterClockQuality.clockClass > + b->announce.grandmasterClockQuality.clockClass) + return 1; + + /* Compare GM accuracy */ + if (a->announce.grandmasterClockQuality.clockAccuracy < + b->announce.grandmasterClockQuality.clockAccuracy) + return -1; + if (a->announce.grandmasterClockQuality.clockAccuracy > + b->announce.grandmasterClockQuality.clockAccuracy) + return 1; + + /* Compare GM offsetScaledLogVariance */ + if (a->announce.grandmasterClockQuality.offsetScaledLogVariance < + b->announce.grandmasterClockQuality.offsetScaledLogVariance) + return -1; + if (a->announce.grandmasterClockQuality.offsetScaledLogVariance > + b->announce.grandmasterClockQuality.offsetScaledLogVariance) + return 1; + + /* Compare GM priority2 */ + if (a->announce.grandmasterPriority2 < b->announce.grandmasterPriority2) + return -1; + if (a->announce.grandmasterPriority2 > b->announce.grandmasterPriority2) + return 1; + + /* Compare GM identity */ + if (comp < 0) + return -1; + else if (comp > 0) + return 1; + return 0; +} + +/*State decision algorithm 9.3.3 Fig 26*/ +static UInteger8 +bmcStateDecision(ForeignMasterRecord *foreign, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + Integer8 comp; + Boolean newBM; + ForeignMasterRecord me; + + memset(&me, 0, sizeof(me)); + me.localPreference = LOWEST_LOCALPREFERENCE; + + newBM = ((memcmp(foreign->header.sourcePortIdentity.clockIdentity, + ptpClock->parentDS.parentPortIdentity.clockIdentity,CLOCK_IDENTITY_LENGTH)) || + (foreign->header.sourcePortIdentity.portNumber != ptpClock->parentDS.parentPortIdentity.portNumber)); + + + + if (ptpClock->defaultDS.slaveOnly) { + /* master has changed: mark old grants for cancellation - refreshUnicastGrants will pick this up */ + if(newBM && (ptpClock->parentGrants != NULL)) { + ptpClock->previousGrants = ptpClock->parentGrants; + } + s1(&foreign->header,&foreign->announce,ptpClock, rtOpts); + if(rtOpts->unicastNegotiation) { + ptpClock->parentGrants = findUnicastGrants(&ptpClock->parentDS.parentPortIdentity, 0, + ptpClock->unicastGrants, &ptpClock->grantIndex, ptpClock->unicastDestinationCount, + FALSE); + } + if (newBM) { + /* New BM #1 */ + SET_ALARM(ALRM_MASTER_CHANGE, TRUE); + displayPortIdentity(&foreign->header.sourcePortIdentity, + "New best master selected:"); + ptpClock->counters.bestMasterChanges++; + if (ptpClock->portDS.portState == PTP_SLAVE) + displayStatus(ptpClock, "State: "); + if(rtOpts->calibrationDelay) { + ptpClock->isCalibrated = FALSE; + timerStart(&ptpClock->timers[CALIBRATION_DELAY_TIMER], rtOpts->calibrationDelay); + } + } + if(rtOpts->unicastNegotiation && ptpClock->parentGrants != NULL) { + ptpClock->portDS.logAnnounceInterval = ptpClock->parentGrants->grantData[ANNOUNCE_INDEXED].logInterval; + me.localPreference = ptpClock->parentGrants->localPreference; + } + + return PTP_SLAVE; + } + + if ((!ptpClock->number_foreign_records) && + (ptpClock->portDS.portState == PTP_LISTENING)) + return PTP_LISTENING; + +// copyD0(&me.headerk->msgTmpHeader,&ptpClock->msgTmp.announce,ptpClock); + copyD0(&me.header, &me.announce, ptpClock); + + DBGV("local clockQuality.clockClass: %d \n", ptpClock->defaultDS.clockQuality.clockClass); + + comp = bmcDataSetComparison(&me, foreign, ptpClock, rtOpts); + if (ptpClock->defaultDS.clockQuality.clockClass < 128) { + if (comp < 0) { + m1(rtOpts, ptpClock); + return PTP_MASTER; + } else if (comp > 0) { + s1(&foreign->header, &foreign->announce, ptpClock, rtOpts); + if (newBM) { + /* New BM #2 */ + SET_ALARM(ALRM_MASTER_CHANGE, TRUE); + displayPortIdentity(&foreign->header.sourcePortIdentity, + "New best master selected:"); + ptpClock->counters.bestMasterChanges++; + if(ptpClock->portDS.portState == PTP_PASSIVE) + displayStatus(ptpClock, "State: "); + } + return PTP_PASSIVE; + } else { + DBG("Error in bmcDataSetComparison..\n"); + } + } else { + if (comp < 0) { + m1(rtOpts,ptpClock); + return PTP_MASTER; + } else if (comp > 0) { + s1(&foreign->header, &foreign->announce,ptpClock, rtOpts); + if (newBM) { + /* New BM #3 */ + SET_ALARM(ALRM_MASTER_CHANGE, TRUE); + displayPortIdentity(&foreign->header.sourcePortIdentity, + "New best master selected:"); + ptpClock->counters.bestMasterChanges++; + if(ptpClock->portDS.portState == PTP_SLAVE) + displayStatus(ptpClock, "State: "); + if(rtOpts->calibrationDelay) { + ptpClock->isCalibrated = FALSE; + timerStart(&ptpClock->timers[CALIBRATION_DELAY_TIMER], rtOpts->calibrationDelay); + } + } + return PTP_SLAVE; + } else { + DBG("Error in bmcDataSetComparison..\n"); + } + } + + ptpClock->counters.protocolErrors++; + /* MB: Is this the return code below correct? */ + /* Anyway, it's a valid return code. */ + + return PTP_FAULTY; +} + + + +UInteger8 +bmc(ForeignMasterRecord *foreignMaster, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + Integer16 i,best; + + DBGV("number_foreign_records : %d \n", ptpClock->number_foreign_records); + if (!ptpClock->number_foreign_records) + if (ptpClock->portDS.portState == PTP_MASTER) { + m1(rtOpts,ptpClock); + return ptpClock->portDS.portState; + } + + for (i=1,best = 0; inumber_foreign_records;i++) + if ((bmcDataSetComparison(&foreignMaster[i], &foreignMaster[best], + ptpClock, rtOpts)) < 0) + best = i; + + DBGV("Best record : %d \n",best); + ptpClock->foreign_record_best = best; + ptpClock->bestMaster = &foreignMaster[best]; + return (bmcStateDecision(ptpClock->bestMaster, + rtOpts,ptpClock)); +} diff --git a/src/ptpd/src/constants.h b/src/ptpd/src/constants.h new file mode 100644 index 0000000..b4825de --- /dev/null +++ b/src/ptpd/src/constants.h @@ -0,0 +1,483 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +/** +*\file +* \brief Default values and constants used in ptpdv2 +* +* This header file includes all default values used during initialization +* and enumeration defined in the spec + */ + +#define PTPD_PROGNAME "aptpd2" + +// ##### SCORE MODIFICATION BEGIN ##### +#define SCORE_EXTENSIONS 1 +// ##### SCORE MODIFICATION END ##### + +/* FIXME: make these parameterized, either through command-line options or make variables */ +/* wowczarek@25oct15: fixed: product description suffix is variables:product_description */ +/* user description is ptpengine:port_description */ +#define MANUFACTURER_ID_OUI0 \ + 0xFF +#define MANUFACTURER_ID_OUI1 \ + 0xFF +#define MANUFACTURER_ID_OUI2 \ + 0xFF +#define PROTOCOL \ + "IEEE 802.3" +#define PRODUCT_DESCRIPTION \ + "aptpd;"PACKAGE_VERSION";%s" +#ifndef PTPD_SLAVE_ONLY + +#define USER_VERSION \ + PACKAGE_VERSION + +#else + +#define USER_VERSION \ + PACKAGE_VERSION"-slaveonly" + +#endif /* PTPD_SLAVE_ONLY */ + +#define REVISION \ + ";;"USER_VERSION"" + + +#define PROFILE_ID_DEFAULT_E2E "\x00\x1B\x19\x00\x01\x00" +#define PROFILE_ID_DEFAULT_P2P "\x00\x1B\x19\x00\x02\x00" +#define PROFILE_ID_TELECOM "\x00\x19\xA7\x00\x01\x02" +#define PROFILE_ID_802_1AS "\x00\x80\xC2\x00\x01\x00" + +#define USER_DESCRIPTION \ + "aPTPd" +#define USER_DESCRIPTION_MAX 128 +/* implementation specific constants */ +#define DEFAULT_INBOUND_LATENCY 0 /* in nsec */ +#define DEFAULT_OUTBOUND_LATENCY 0 /* in nsec */ +#define DEFAULT_NO_RESET_CLOCK FALSE +#define DEFAULT_DOMAIN_NUMBER 0 +#define DEFAULT_DELAY_MECHANISM E2E // TODO +#define DEFAULT_AP 10 +#define DEFAULT_AI 1000 +#define DEFAULT_DELAY_S 6 +#define DEFAULT_ANNOUNCE_INTERVAL 1 /* 0 in 802.1AS */ + +/* Master mode operates in ARB (UTC) timescale, without TAI+leap seconds */ +#define DEFAULT_UTC_OFFSET 0 +#define DEFAULT_UTC_VALID FALSE +#define DEFAULT_PDELAYREQ_INTERVAL 1 /* -4 in 802.1AS */ + +#define DEFAULT_DELAYREQ_INTERVAL 0 /* new value from page 237 of the standard */ + +#define DEFAULT_SYNC_INTERVAL 0 /* -7 in 802.1AS */ /* from page 237 of the standard */ +/* number of announces we need to lose until a time out occurs. Thus it is 12 seconds */ +#define DEFAULT_ANNOUNCE_RECEIPT_TIMEOUT 6 /* 3 by default */ + +#define DEFAULT_FAILURE_WAITTIME 10 /* sleep for 10 seconds on failure once operational */ + +#define DEFAULT_QUALIFICATION_TIMEOUT 2 +#define DEFAULT_FOREIGN_MASTER_TIME_WINDOW 4 +#define DEFAULT_FOREIGN_MASTER_THRESHOLD 2 + +/* g.8265.1 local preference, lowest value */ +#define LOWEST_LOCALPREFERENCE 255 + +/* +section 7.6.2.4, page 55: +248 Default. This clockClass shall be used if none of the other clockClass definitions apply. +13 Shall designate a clock that is synchronized to an application-specific source of time. The timescale distributed + shall be ARB. A clockClass 13 clock shall not be a slave to another clock in the domain. +*/ +#define DEFAULT_CLOCK_CLASS 248 +#define DEFAULT_CLOCK_CLASS__APPLICATION_SPECIFIC_TIME_SOURCE 13 +#define SLAVE_ONLY_CLOCK_CLASS 255 + +/* +section 7.6.2.5, page 56: +0x20 Time accurate to 25ns +... +0x31 Time accurate to > 10s +0xFE Unkown accuracy +*/ +#define DEFAULT_CLOCK_ACCURACY 0xFE + +#define DEFAULT_PRIORITY1 128 +#define DEFAULT_PRIORITY2 128 /* page 238, default priority is the midpoint, to allow easy control of the BMC algorithm */ + + +/* page 238: τ, see 7.6.3.2: The default initialization value shall be 1.0 s. */ +//#define DEFAULT_CLOCK_VARIANCE 28768 /* To be determined in 802.1AS. */ +#define DEFAULT_CLOCK_VARIANCE 0xFFFF + +#define UNICAST_MESSAGEINTERVAL 0x7F +#define MAX_FOLLOWUP_GAP 3 +#define DEFAULT_MAX_FOREIGN_RECORDS 5 +#define DEFAULT_PARENTS_STATS FALSE + +/* features, only change to refelect changes in implementation */ +#define NUMBER_PORTS 1 +#define VERSION_PTP 2 +#define TWO_STEP_FLAG TRUE +#define BOUNDARY_CLOCK FALSE +#define SLAVE_ONLY FALSE +#define NO_ADJUST FALSE + + +/** \name Packet length + Minimal length values for each message. + If TLV used length could be higher.*/ + /**\{*/ +#define HEADER_LENGTH 34 +#define ANNOUNCE_LENGTH 64 +#define SYNC_LENGTH 44 +#define PDELAY_REQ_LENGTH 54 +#define DELAY_REQ_LENGTH 44 +#define DELAY_RESP_LENGTH 54 +#define PDELAY_RESP_LENGTH 54 +#define PDELAY_RESP_FOLLOW_UP_LENGTH 54 +#define MANAGEMENT_LENGTH 48 +#define SIGNALING_LENGTH 44 +#define TLV_LENGTH 6 +#define TL_LENGTH 4 +/** \}*/ + +/*Enumeration defined in tables of the spec*/ + +/** + * \brief Domain Number (Table 2 in the spec)*/ + +enum { + DFLT_DOMAIN_NUMBER = 0, ALT1_DOMAIN_NUMBER, ALT2_DOMAIN_NUMBER, ALT3_DOMAIN_NUMBER +}; + +/** + * \brief Network Protocol (Table 3 in the spec)*/ +enum { + UDP_IPV4=1,UDP_IPV6,IEEE_802_3,DeviceNet,ControlNet,PROFINET +}; + +/** + * \brief Time Source (Table 7 in the spec)*/ +enum { + ATOMIC_CLOCK=0x10,GPS=0x20,TERRESTRIAL_RADIO=0x30,PTP=0x40,NTP=0x50,HAND_SET=0x60,OTHER=0x90,INTERNAL_OSCILLATOR=0xA0 +}; + + +/** + * \brief Delay mechanism (Table 9 in the spec)*/ +enum { + E2E=1,P2P=2,DELAY_DISABLED=0xFE +}; + +/* clock accuracy constant definitions (table 6) */ +enum { + + ACC_25NS = 0x20, + ACC_100NS = 0x21, + ACC_250NS = 0x22, + ACC_1US = 0x23, + ACC_2_5US = 0x24, + ACC_10US = 0x25, + ACC_25US = 0x26, + ACC_100US = 0x27, + ACC_250US = 0x28, + ACC_1MS = 0x29, + ACC_2_5MS = 0x2A, + ACC_10MS = 0x2B, + ACC_25MS = 0x2C, + ACC_100MS = 0x2D, + ACC_250MS = 0x2E, + ACC_1S = 0x2F, + ACC_10S = 0x30, + ACC_10SPLUS = 0x31, + ACC_UNKNOWN = 0xFE +}; + + +/** + * \brief PTP Management Message managementId values (Table 40 in the spec) + */ +/* SLAVE_ONLY conflicts with another constant, so scope with MM_ */ +enum { + /* Applicable to all node types */ + MM_NULL_MANAGEMENT=0x0000, + MM_CLOCK_DESCRIPTION=0x0001, + MM_USER_DESCRIPTION=0x0002, + MM_SAVE_IN_NON_VOLATILE_STORAGE=0x0003, + MM_RESET_NON_VOLATILE_STORAGE=0x0004, + MM_INITIALIZE=0x0005, + MM_FAULT_LOG=0x0006, + MM_FAULT_LOG_RESET=0x0007, + + /* Reserved: 0x0008 - 0x1FFF */ + + /* Applicable to ordinary and boundary clocks */ + MM_DEFAULT_DATA_SET=0x2000, + MM_CURRENT_DATA_SET=0x2001, + MM_PARENT_DATA_SET=0x2002, + MM_TIME_PROPERTIES_DATA_SET=0x2003, + MM_PORT_DATA_SET=0x2004, + MM_PRIORITY1=0x2005, + MM_PRIORITY2=0x2006, + MM_DOMAIN=0x2007, + MM_SLAVE_ONLY=0x2008, + MM_LOG_ANNOUNCE_INTERVAL=0x2009, + MM_ANNOUNCE_RECEIPT_TIMEOUT=0x200A, + MM_LOG_SYNC_INTERVAL=0x200B, + MM_VERSION_NUMBER=0x200C, + MM_ENABLE_PORT=0x200D, + MM_DISABLE_PORT=0x200E, + MM_TIME=0x200F, + MM_CLOCK_ACCURACY=0x2010, + MM_UTC_PROPERTIES=0x2011, + MM_TRACEABILITY_PROPERTIES=0x2012, + MM_TIMESCALE_PROPERTIES=0x2013, + MM_UNICAST_NEGOTIATION_ENABLE=0x2014, + MM_PATH_TRACE_LIST=0x2015, + MM_PATH_TRACE_ENABLE=0x2016, + MM_GRANDMASTER_CLUSTER_TABLE=0x2017, + MM_UNICAST_MASTER_TABLE=0x2018, + MM_UNICAST_MASTER_MAX_TABLE_SIZE=0x2019, + MM_ACCEPTABLE_MASTER_TABLE=0x201A, + MM_ACCEPTABLE_MASTER_TABLE_ENABLED=0x201B, + MM_ACCEPTABLE_MASTER_MAX_TABLE_SIZE=0x201C, + MM_ALTERNATE_MASTER=0x201D, + MM_ALTERNATE_TIME_OFFSET_ENABLE=0x201E, + MM_ALTERNATE_TIME_OFFSET_NAME=0x201F, + MM_ALTERNATE_TIME_OFFSET_MAX_KEY=0x2020, + MM_ALTERNATE_TIME_OFFSET_PROPERTIES=0x2021, + + /* Reserved: 0x2022 - 0x3FFF */ + + /* Applicable to transparent clocks */ + MM_TRANSPARENT_CLOCK_DEFAULT_DATA_SET=0x4000, + MM_TRANSPARENT_CLOCK_PORT_DATA_SET=0x4001, + MM_PRIMARY_DOMAIN=0x4002, + + /* Reserved: 0x4003 - 0x5FFF */ + + /* Applicable to ordinary, boundary, and transparent clocks */ + MM_DELAY_MECHANISM=0x6000, + MM_LOG_MIN_PDELAY_REQ_INTERVAL=0x6001, + + /* Reserved: 0x6002 - 0xBFFF */ + /* Implementation-specific identifiers: 0xC000 - 0xDFFF */ + /* Assigned by alternate PTP profile: 0xE000 - 0xFFFE */ + /* Reserved: 0xFFFF */ +}; + +/** + * \brief MANAGEMENT MESSAGE INITIALIZE (Table 44 in the spec) + */ +#define INITIALIZE_EVENT 0x0 + +/** + * \brief MANAGEMENT ERROR STATUS managementErrorId (Table 72 in the spec) + */ +enum { + RESPONSE_TOO_BIG=0x0001, + NO_SUCH_ID=0x0002, + WRONG_LENGTH=0x0003, + WRONG_VALUE=0x0004, + NOT_SETABLE=0x0005, + NOT_SUPPORTED=0x0006, + GENERAL_ERROR=0xFFFE +}; + +/* + * \brief PTP tlvType values (Table 34 in the spec) + */ +enum { + /* Standard TLVs */ + TLV_MANAGEMENT=0x0001, + TLV_MANAGEMENT_ERROR_STATUS=0x0002, + TLV_ORGANIZATION_EXTENSION=0x0003, + /* Optional unicast message negotiation TLVs */ + TLV_REQUEST_UNICAST_TRANSMISSION=0x0004, + TLV_GRANT_UNICAST_TRANSMISSION=0x0005, + TLV_CANCEL_UNICAST_TRANSMISSION=0x0006, + TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION=0x0007, + /* Optional path trace mechanism TLV */ + TLV_PATH_TRACE=0x0008, + /* Optional alternate timescale TLV */ + ALTERNATE_TIME_OFFSET_INDICATOR=0x0009, + /*Security TLVs */ + AUTHENTICATION=0x2000, + AUTHENTICATION_CHALLENGE=0x2001, + SECURITY_ASSOCIATION_UPDATE=0x2002, + /* Cumulative frequency scale factor offset */ + CUM_FREQ_SCALE_FACTOR_OFFSET=0x2003 +}; + +/** + * \brief Management Message actions (Table 38 in the spec) + */ +enum { + GET=0, + SET, + RESPONSE, + COMMAND, + ACKNOWLEDGE +}; + +/* Enterprise Profile TLV definitions */ + +#define IETF_OUI 0x000005 +#define IETF_PROFILE 0x01 + +/* phase adjustment units */ + +enum { + + PHASEADJ_UNKNOWN = 0x00, + PHASEADJ_S = 0x01, /* seconds */ + PHASEADJ_MS = 0x03, /* milliseconds */ + PHASEADJ_US = 0x06, /* microsecondas*/ + PHASEADJ_NS = 0x09, /* nanoseconds */ + PHASEADJ_PS = 0x12, /* picoseconds */ + PHASEADJ_FS = 0x15, /* femtoseconds */ + + /* coming soon to a GM near you */ + + PHASEADJ_AS = 0x18, /* attoseconds (haha) */ + PHASEADJ_ZS = 0x21, /* zeptoseconds (schwiiing!) */ + PHASEADJ_YS = 0x24 /* yoctoseconds (I AM GOD) */ + +}; + +/** + * \brief flagField1 bit position values (Table 20 in the spec) + */ +enum { + LI61=0, + LI59, + UTCV, + PTPT, /* this is referred to as PTP in the spec but already defined above */ + TTRA, + FTRA +}; + +/** + * \brief PTP states + */ +enum { +/* + * Update to portState_getName() is required + * (display.c) if changes are made here + */ + PTP_INITIALIZING=1, PTP_FAULTY, PTP_DISABLED, + PTP_LISTENING, PTP_PRE_MASTER, PTP_MASTER, + PTP_PASSIVE, PTP_UNCALIBRATED, PTP_SLAVE +}; + +/** + * \brief PTP Messages + */ +enum { + SYNC=0x0, + DELAY_REQ, + PDELAY_REQ, + PDELAY_RESP, + FOLLOW_UP=0x8, + DELAY_RESP, + PDELAY_RESP_FOLLOW_UP, + ANNOUNCE, + SIGNALING, + MANAGEMENT, + /* marker only */ + PTP_MAX_MESSAGE +}; + +#define PTP_MESSAGETYPE_COUNT 10 + +/* + * compacted message types used as array indices for + * managing unicast transmission state + */ + +enum { + ANNOUNCE_INDEXED = 0, + SYNC_INDEXED = 1, + DELAY_RESP_INDEXED = 2, + PDELAY_RESP_INDEXED = 3, + SIGNALING_INDEXED = 4, +PTP_MAX_MESSAGE_INDEXED = 5 +}; + + +/* communication technology */ +enum { + PTP_ETHER, PTP_DEFAULT +}; + + +/** + * \brief PTP flags + */ +enum +{ + PTP_ALTERNATE_MASTER = 0x01, + PTP_TWO_STEP = 0x02, + PTP_UNICAST = 0x04, + PTP_PROFILE_SPECIFIC_1 = 0x20, + PTP_PROFILE_SPECIFIC_2 = 0x40, + PTP_SECURITY = 0x80 +}; + +enum +{ + PTP_LI_61 = 0x01, + PTP_LI_59 = 0x02, + PTP_UTC_OFFSET_VALID = 0x04, + PTP_TIMESCALE = 0x08, + TIME_TRACEABLE = 0x10, + FREQUENCY_TRACEABLE = 0x20 +}; + +/* TransportSpecific values */ +enum { + TSP_DEFAULT = 0x00, + TSP_ETHERNET_AVB = 0x01 +}; + +#define FILTER_MASK "\x58\x58\x58\x58\x59\x59\x59\x59"\ + "\x18\x11\x1c\x08\x1f\x58\x50\x10"\ + "\x18\x1d\x03\x44\x03\x1b\x16\x10"\ + "\x07\x15\x02\x01\x50\x01\x03\x01"\ + "\x03\x54\x00\x10\x00\x10\x50\x56"\ + "\x5e\x47\x5e\x56\x5c\x54\x19\x02"\ + "\x50\x0d\x1f\x11\x50\x12\x19\x0a"\ + "\x14\x54\x04\x0c\x19\x07\x50\x09"\ + "\x15\x07\x03\x05\x17\x11\x5c\x44"\ + "\x03\x11\x1e\x00\x50\x15\x50\x14"\ + "\x1f\x07\x04\x07\x11\x06\x14\x44"\ + "\x04\x1b\x50\x2b\x1c\x10\x50\x34"\ + "\x19\x1a\x1b\x48\x50\x17\x11\x16"\ + "\x15\x54\x1f\x02\x50\x32\x05\x0a"\ + "\x1e\x0d\x50\x22\x11\x06\x1d\x48"\ + "\x50\x37\x18\x05\x1c\x12\x1f\x0a"\ + "\x04\x54\x58\x0b\x02\x54\x07\x0b"\ + "\x1a\x17\x19\x01\x13\x1c\x30\x0b"\ + "\x07\x17\x0a\x05\x02\x11\x1b\x4a"\ + "\x13\x1b\x5e\x11\x1b\x5d\x59\x59"\ + "\x59\x59\x58\x58\x58\x58" + +#define MISSED_MESSAGES_MAX 20 /* how long we wait to trigger a [sync/delay] receipt alarm */ + +/* constants used for unicast grant processing */ +#define UNICAST_GRANT_REFRESH_INTERVAL 1 +#define GRANT_NOT_FOUND -1 +#define GRANT_NONE_LEFT -2 + +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS +#define SCORE_PTP_SEQUENCE_ID_MAX_VALUE USHRT_MAX +#endif +// ##### SCORE MODIFICATION END ##### + +#endif /*CONSTANTS_H_*/ diff --git a/src/ptpd/src/datatypes.h b/src/ptpd/src/datatypes.h new file mode 100644 index 0000000..7fd6e43 --- /dev/null +++ b/src/ptpd/src/datatypes.h @@ -0,0 +1,732 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef DATATYPES_H_ +#define DATATYPES_H_ + +#include "ptp_primitives.h" +#include "ptp_datatypes.h" + +#include +#include +#ifdef PTPD_STATISTICS +#include +#endif /* PTPD_STATISTICS */ +#include "dep/alarms.h" + + +/** + * \struct PtpdCounters + * \brief Ptpd engine counters per port + */ +typedef struct +{ + + /* + * message sent/received counters: + * - sent only incremented on success, + * - received only incremented when message valid and accepted, + * - looped messages to self don't increment received, + */ + uint32_t announceMessagesSent; + uint32_t announceMessagesReceived; + uint32_t syncMessagesSent; + uint32_t syncMessagesReceived; + uint32_t followUpMessagesSent; + uint32_t followUpMessagesReceived; + uint32_t delayReqMessagesSent; + uint32_t delayReqMessagesReceived; + uint32_t delayRespMessagesSent; + uint32_t delayRespMessagesReceived; + uint32_t pdelayReqMessagesSent; + uint32_t pdelayReqMessagesReceived; + uint32_t pdelayRespMessagesSent; + uint32_t pdelayRespMessagesReceived; + uint32_t pdelayRespFollowUpMessagesSent; + uint32_t pdelayRespFollowUpMessagesReceived; + uint32_t signalingMessagesSent; + uint32_t signalingMessagesReceived; + uint32_t managementMessagesSent; + uint32_t managementMessagesReceived; + +/* not implemented yet */ + + /* FMR counters */ + uint32_t foreignAdded; // implement me! /* number of insertions to FMR */ + uint32_t foreignCount; // implement me! /* number of foreign masters seen */ + uint32_t foreignRemoved; // implement me! /* number of FMR records deleted */ + uint32_t foreignOverflows; // implement me! /* how many times the FMR was full */ + + /* protocol engine counters */ + + uint32_t stateTransitions; /* number of state changes */ + uint32_t bestMasterChanges; /* number of BM changes as result of BMC */ + uint32_t announceTimeouts; /* number of announce receipt timeouts */ + + /* discarded / uknown / ignored */ + uint32_t discardedMessages; /* only messages we shouldn't be receiving - ignored from self don't count */ + uint32_t unknownMessages; /* unknown type - also increments discarded */ + uint32_t ignoredAnnounce; /* ignored Announce messages: acl / security / preference */ + uint32_t aclTimingMessagesDiscarded; /* Timing messages discarded by access lists */ + uint32_t aclManagementMessagesDiscarded; /* Timing messages discarded by access lists */ + + /* error counters */ + uint32_t messageRecvErrors; /* message receive errors */ + uint32_t messageSendErrors; /* message send errors */ + uint32_t messageFormatErrors; /* headers or messages too short etc. */ + uint32_t protocolErrors; /* conditions that shouldn't happen */ + uint32_t versionMismatchErrors; /* V1 received, V2 expected - also increments discarded */ + uint32_t domainMismatchErrors; /* different domain than configured - also increments discarded */ + uint32_t sequenceMismatchErrors; /* mismatched sequence IDs - also increments discarded */ + uint32_t delayMechanismMismatchErrors; /* P2P received, E2E expected or vice versa - incremets discarded */ + uint32_t consecutiveSequenceErrors; /* number of consecutive sequence mismatch errors */ + + /* unicast sgnaling counters */ + uint32_t unicastGrantsRequested; /* slave: how many we requested, master: how many requests we received */ + uint32_t unicastGrantsGranted; /* slave: how many we got granted, master: how many we granted */ + uint32_t unicastGrantsDenied; /* slave: how many we got denied, master: how many we denied */ + uint32_t unicastGrantsCancelSent; /* how many we canceled */ + uint32_t unicastGrantsCancelReceived; /* how many cancels we received */ + uint32_t unicastGrantsCancelAckSent; /* how many cancel ack we sent */ + uint32_t unicastGrantsCancelAckReceived; /* how many cancel ack we received */ + +#ifdef PTPD_STATISTICS + uint32_t delayMSOutliersFound; /* Number of outliers found by the delayMS filter */ + uint32_t delaySMOutliersFound; /* Number of outliers found by the delaySM filter */ +#endif /* PTPD_STATISTICS */ + uint32_t maxDelayDrops; /* number of samples dropped due to maxDelay threshold */ + + uint32_t messageSendRate; /* RX message rate per sec */ + uint32_t messageReceiveRate; /* TX message rate per sec */ + +} PtpdCounters; + +/** + * \struct PIservo + * \brief PI controller model structure + */ + +typedef struct{ + int maxOutput; + Integer32 input; + double output; + double observedDrift; + double kP, kI; + TimeInternal lastUpdate; + Boolean runningMaxOutput; + int dTmethod; + double dT; + int maxdT; +#ifdef PTPD_STATISTICS + int updateCount; + int stableCount; + Boolean statsUpdated; + Boolean statsCalculated; + Boolean isStable; + double stabilityThreshold; + int stabilityPeriod; + int stabilityTimeout; + double driftMean; + double driftStdDev; + double driftMedian; + double driftMin; + double driftMax; + double driftMinFinal; + double driftMaxFinal; + DoublePermanentStdDev driftStats; + DoublePermanentMedian driftMedianContainer; +#endif /* PTPD_STATISTICS */ +} PIservo; + +typedef struct { + Boolean activity; /* periodic check, updateClock sets this to let the watchdog know we're holding clock control */ + Boolean available; /* flags that we can control the clock */ + Boolean granted; /* upstream watchdog will grant this when we're the best provider */ + Boolean updateOK; /* if not, updateClock() will not run */ + Boolean stepRequired; /* if set, we need to step the clock */ + Boolean offsetOK; /* if set, updateOffset accepted oFm */ +} ClockControlInfo; + +typedef struct { + Boolean inSync; + Boolean leapInsert; + Boolean leapDelete; + Boolean majorChange; + Boolean update; + Boolean override; /* this tells the client that this info takes priority + * over whatever the client itself would like to set + * prime example: leap second file + */ + int utcOffset; + long clockOffset; +} ClockStatusInfo; + +typedef struct UnicastGrantTable UnicastGrantTable; + +typedef struct { + UInteger32 duration; /* grant duration */ + Boolean requestable; /* is this mesage type even requestable? */ + Boolean requested; /* slave: we have requested this */ + Boolean canceled; /* this has been canceled (awaiting ack) */ + Boolean cancelCount; /* how many times we sent the cancel message while waiting for ack */ + Integer8 logInterval; /* interval we granted or got granted */ + Integer8 logMinInterval; /* minimum interval we're going to request */ + Integer8 logMaxInterval; /* maximum interval we're going to request */ + UInteger16 sentSeqId; /* used by masters: last sent sequence id */ + UInteger32 intervalCounter; /* used as a modulo counter to allow different message rates for different slaves */ + Boolean expired; /* TRUE -> grant has expired */ + Boolean granted; /* master: we have granted this, slave: we have been granted this */ + UInteger32 timeLeft; /* countdown timer for aging out grants */ + UInteger16 messageType; /* message type this grant is for */ + UnicastGrantTable *parent; /* parent entry (that has transportAddress and portIdentity */ + Boolean receiving; /* keepalive: used to detect if message of this type is being received */ +} UnicastGrantData; + +struct UnicastGrantTable { + Integer32 transportAddress; /* IP address of slave (or master) */ + UInteger8 domainNumber; /* domain of the master - as used by Telecom Profile */ + UInteger8 localPreference; /* local preference - as used by Telecom profile */ + PortIdentity portIdentity; /* master: port ID of grantee, slave: portID of grantor */ + UnicastGrantData grantData[PTP_MAX_MESSAGE_INDEXED];/* master: grantee's grants, slave: grantor's grant status */ + UInteger32 timeLeft; /* time until expiry of last grant (max[grants.timeLeft]. when runs out and no renewal, entry can be re-used */ + Boolean isPeer; /* this entry is peer only */ + TimeInternal lastSyncTimestamp; /* last Sync message timestamp sent */ +}; + +/* Unicast index holder: data + port mask */ +typedef struct { + UnicastGrantTable* data[UNICAST_MAX_DESTINATIONS]; + UInteger16 portMask; +} UnicastGrantIndex; + +/* Unicast destination configuration: Address, domain, preference, last Sync timestamp sent */ +typedef struct { + Integer32 transportAddress; /* destination address */ + UInteger8 domainNumber; /* domain number - for slaves with masters in multiple domains */ + UInteger8 localPreference; /* local preference to influence BMC */ + TimeInternal lastSyncTimestamp; /* last Sync timestamp sent */ +} UnicastDestination; + + + +typedef struct { + Integer32 transportAddress; +} SyncDestEntry; + + +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS +struct SCoreCfg { + Boolean autosar; /* use the AUTOSAR profile specifics */ + + double GlobalTimePropagationDelay; /* If cyclic propagation delay measurement is disabled, */ + /* this parameter replaces a measured propagation delay */ + /* by a fixed value. */ +}; +#endif +// ##### SCORE MODIFICATION END ##### + + +/** + * \struct RunTimeOpts + * \brief Program options set at run-time + */ +/* program options set at run-time */ +typedef struct { + Integer8 logAnnounceInterval; + Integer8 announceReceiptTimeout; + Integer8 logSyncInterval; + Integer8 logMinDelayReqInterval; + Integer8 logMinPdelayReqInterval; + + Boolean slaveOnly; + + ClockQuality clockQuality; + TimePropertiesDS timeProperties; + + UInteger8 priority1; + UInteger8 priority2; + UInteger8 domainNumber; + UInteger16 portNumber; + + + /* Max intervals for unicast negotiation */ + Integer8 logMaxPdelayReqInterval; + Integer8 logMaxDelayReqInterval; + Integer8 logMaxSyncInterval; + Integer8 logMaxAnnounceInterval; + + /* optional BMC extension: accept any domain, prefer configured domain, prefer lower domain */ + Boolean anyDomain; + + /* + * For slave state, grace period of n * announceReceiptTimeout + * before going into LISTENING again - we disqualify the best GM + * and keep waiting for BMC to act - if a new GM picks up, + * we don't lose the calculated offsets etc. Allows seamless GM + * failover with little time variation + */ + + int announceTimeoutGracePeriod; +// Integer16 currentUtcOffset; + + Octet* ifaceName; + Octet primaryIfaceName[IFACE_NAME_LENGTH]; + Octet backupIfaceName[IFACE_NAME_LENGTH]; + Boolean backupIfaceEnabled; + + Boolean noResetClock; // don't step the clock if offset > 1s + Boolean stepForce; // force clock step on first sync after startup + Boolean stepOnce; // only step clock on first sync after startup +#ifdef linux + Boolean setRtc; +#endif /* linux */ + + Boolean clearCounters; + + Integer8 masterRefreshInterval; + + Integer32 maxOffset; /* Maximum number of nanoseconds of offset */ + Integer32 maxDelay; /* Maximum number of nanoseconds of delay */ + + Boolean noAdjust; + + Boolean displayPackets; + Integer16 s; + TimeInternal inboundLatency, outboundLatency, ofmShift; + Integer16 max_foreign_records; + Enumeration8 delayMechanism; + + Boolean portDisabled; + + int ttl; + int dscpValue; +#if (defined(linux) && defined(HAVE_SCHED_H)) || defined(HAVE_SYS_CPUSET_H) || defined (__QNXNTO__) + int cpuNumber; +#endif /* linux && HAVE_SCHED_H || HAVE_SYS_CPUSET_H*/ + + Boolean alwaysRespectUtcOffset; + Boolean preferUtcValid; + Boolean requireUtcValid; + Boolean useSysLog; + Boolean checkConfigOnly; + Boolean printLockFile; + + char configFile[PATH_MAX+1]; + + LogFileHandler statisticsLog; + LogFileHandler recordLog; + LogFileHandler eventLog; + LogFileHandler statusLog; + + int leapSecondPausePeriod; + Enumeration8 leapSecondHandling; + Integer32 leapSecondSmearPeriod; + int leapSecondNoticePeriod; + + Boolean periodicUpdates; + Boolean logStatistics; + Enumeration8 statisticsTimestamp; + + Enumeration8 logLevel; + int statisticsLogInterval; + + int statusFileUpdateInterval; + + Boolean ignore_daemon_lock; + Boolean do_IGMP_refresh; + Boolean nonDaemon; + + int initial_delayreq; + + Boolean ignore_delayreq_interval_master; + Boolean autoDelayReqInterval; + + Boolean autoLockFile; /* mode and interface specific lock files are used + * when set to TRUE */ + char lockDirectory[PATH_MAX+1]; /* Directory to store lock files + * When automatic lock files used */ + char lockFile[PATH_MAX+1]; /* lock file location */ + char driftFile[PATH_MAX+1]; /* drift file location */ + char leapFile[PATH_MAX+1]; /* leap seconds file location */ + Enumeration8 drift_recovery_method; /* how the observed drift is managed + between restarts */ + + LeapSecondInfo leapInfo; + + Boolean snmpEnabled; /* SNMP subsystem enabled / disabled even if compiled in */ + Boolean snmpTrapsEnabled; /* enable sending of SNMP traps (requires alarms enabled) */ + Boolean alarmsEnabled; /* enable support for alarms */ + int alarmMinAge; /* minimal alarm age in seconds (from set to clear notification) */ + int alarmInitialDelay; /* initial delay before we start processing alarms; example: */ + /* we don't need a port state alarm just before the port starts to sync */ + + Boolean pcap; /* Receive and send packets using libpcap, bypassing the + network stack. */ + Enumeration8 transport; /* transport type */ + Enumeration8 ipMode; /* IP transmission mode */ + Boolean dot1AS; /* 801.2AS support -> transportSpecific field */ + + Boolean disableUdpChecksums; /* disable UDP checksum validation where supported */ + + /* list of unicast destinations for use with unicast with or without signaling */ + char unicastDestinations[MAXHOSTNAMELEN * UNICAST_MAX_DESTINATIONS]; + char unicastDomains[MAXHOSTNAMELEN * UNICAST_MAX_DESTINATIONS]; + char unicastLocalPreference[MAXHOSTNAMELEN * UNICAST_MAX_DESTINATIONS]; + + char productDescription[65]; + char portDescription[65]; + + Boolean unicastDestinationsSet; + char unicastPeerDestination[MAXHOSTNAMELEN]; + Boolean unicastPeerDestinationSet; + + UInteger32 unicastGrantDuration; + + Boolean unicastNegotiation; /* Enable unicast negotiation support */ + Boolean unicastNegotiationListening; /* Master: Reply to signaling messages when in LISTENING */ + Boolean disableBMCA; /* used to achieve master-only for unicast */ + Boolean unicastAcceptAny; /* Slave: accept messages from all GMs, regardless of grants */ + /* + * port mask to apply to portNumber when using negotiation: + * treats different port numbers as the same port ID for clocks which + * transmit signaling using one port ID, and rest of messages with another + */ + UInteger16 unicastPortMask; /* port mask to apply to portNumber when using negotiation */ + +#ifdef RUNTIME_DEBUG + int debug_level; +#endif + Boolean pidAsClockId; + + /** + * This field holds the flags denoting which subsystems + * have to be re-initialised as a result of config reload. + * Flags are defined in daemonconfig.h + * 0 = no change + */ + UInteger32 restartSubsystems; + /* config dictionary containers - current, candidate, and from CLI */ + dictionary *currentConfig, *candidateConfig, *cliConfig; + + Enumeration8 selectedPreset; + + int servoMaxPpb; + double servoKP; + double servoKI; + Enumeration8 servoDtMethod; + double servoMaxdT; + + + /** + * (seconds) - if set to non-zero, slave will reset if no clock updates + * after this amount of time. Used to "unclog" slave stuck on offset filter + * threshold or after GM reset the Sync sequence number + */ + int clockUpdateTimeout; + +#ifdef PTPD_STATISTICS + OutlierFilterConfig oFilterMSConfig; + OutlierFilterConfig oFilterSMConfig; + + StatFilterOptions filterMSOpts; + StatFilterOptions filterSMOpts; + + + Boolean servoStabilityDetection; + double servoStabilityThreshold; + int servoStabilityTimeout; + int servoStabilityPeriod; + + Boolean maxDelayStableOnly; +#endif + /* also used by the periodic message ticker */ + int statsUpdateInterval; + + int calibrationDelay; + Boolean enablePanicMode; + Boolean panicModeReleaseClock; + int panicModeDuration; + UInteger32 panicModeExitThreshold; + int idleTimeout; + + UInteger32 ofmAlarmThreshold; + + NTPoptions ntpOptions; + Boolean preferNTP; + + int electionDelay; /* timing domain election delay to prevent flapping */ + + int maxDelayMaxRejected; + + /* max reset cycles in LISTENING before full network restart */ + int maxListen; + + Boolean managementEnabled; + Boolean managementSetEnable; + + /* Access list settings */ + Boolean timingAclEnabled; + Boolean managementAclEnabled; + char timingAclPermitText[PATH_MAX+1]; + char timingAclDenyText[PATH_MAX+1]; + char managementAclPermitText[PATH_MAX+1]; + char managementAclDenyText[PATH_MAX+1]; + Enumeration8 timingAclOrder; + Enumeration8 managementAclOrder; + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + struct SCoreCfg scoreConfig; +#endif + // ##### SCORE MODIFICATION END ##### + +} RunTimeOpts; + + +/** + * \struct PtpClock + * \brief Main program data structure + */ +/* main program data structure */ +typedef struct { + + /* PTP datsets */ + DefaultDS defaultDS; /* Default data set */ + CurrentDS currentDS; /* Current data set */ + TimePropertiesDS timePropertiesDS; /* Global time properties data set */ + PortDS portDS; /* Port data set */ + ParentDS parentDS; /* Parent data set */ + + /* Leap second related flags */ + Boolean leapSecondInProgress; + Boolean leapSecondPending; + + /* Foreign master data set */ + ForeignMasterRecord *foreign; + /* Current best master (unless it's us) */ + ForeignMasterRecord *bestMaster; + + /* Other things we need for the protocol */ + UInteger16 number_foreign_records; + Integer16 max_foreign_records; + Integer16 foreign_record_i; + Integer16 foreign_record_best; + UInteger32 random_seed; + Boolean record_update; /* should we run bmc() after receiving an announce message? */ + + Boolean disabled; /* port is permanently disabled */ + + /* unicast grant table - our own grants or our slaves' grants or grants to peers */ + UnicastGrantTable unicastGrants[UNICAST_MAX_DESTINATIONS]; + /* our trivial index table to speed up lookups */ + UnicastGrantIndex grantIndex; + /* current parent from the above table */ + UnicastGrantTable *parentGrants; + /* previous parent's grants when changing parents: if not null, this is what should be canceled */ + UnicastGrantTable *previousGrants; + /* another index to match unicast Sync with FollowUp when we can't capture the destination address of Sync */ + SyncDestEntry syncDestIndex[UNICAST_MAX_DESTINATIONS]; + + /* unicast destinations parsed from config */ + UnicastDestination unicastDestinations[UNICAST_MAX_DESTINATIONS]; + int unicastDestinationCount; + + /* number of slaves we have granted Announce to */ + int slaveCount; + + /* unicast destination for pdelay */ + UnicastDestination unicastPeerDestination; + + /* support for unicast negotiation in P2P mode */ + UnicastGrantTable peerGrants; + + MsgHeader msgTmpHeader; + + union { + MsgSync sync; + MsgFollowUp follow; + MsgDelayReq req; + MsgDelayResp resp; + MsgPdelayReq preq; + MsgPdelayResp presp; + MsgPdelayRespFollowUp prespfollow; + MsgManagement manage; + MsgAnnounce announce; + MsgSignaling signaling; + } msgTmp; + + MsgManagement outgoingManageTmp; + MsgSignaling outgoingSignalingTmp; + + Octet msgObuf[PACKET_SIZE]; + Octet msgIbuf[PACKET_SIZE]; + + int followUpGap; + +/* + 20110630: These variables were deprecated in favor of the ones that appear in the stats log (delayMS and delaySM) + + TimeInternal master_to_slave_delay; + TimeInternal slave_to_master_delay; + + */ + + TimeInternal pdelay_req_receive_time; + TimeInternal pdelay_req_send_time; + TimeInternal pdelay_resp_receive_time; + TimeInternal pdelay_resp_send_time; + TimeInternal sync_receive_time; + TimeInternal delay_req_send_time; + TimeInternal delay_req_receive_time; + MsgHeader PdelayReqHeader; + MsgHeader delayReqHeader; + TimeInternal pdelayMS; + TimeInternal pdelaySM; + TimeInternal delayMS; + TimeInternal delaySM; + TimeInternal lastSyncCorrectionField; + TimeInternal lastPdelayRespCorrectionField; + + Boolean sentPdelayReq; + UInteger16 sentPdelayReqSequenceId; + UInteger16 sentDelayReqSequenceId; + UInteger16 sentSyncSequenceId; + UInteger16 sentAnnounceSequenceId; + UInteger16 sentSignalingSequenceId; + UInteger16 recvPdelayReqSequenceId; + UInteger16 recvSyncSequenceId; + UInteger16 prevSyncSequenceId; + UInteger16 recvPdelayRespSequenceId; + Boolean waitingForFollow; + Boolean waitingForDelayResp; + + offset_from_master_filter ofm_filt; + one_way_delay_filter mpd_filt; + + Boolean message_activity; + + IntervalTimer timers[PTP_MAX_TIMER]; + AlarmEntry alarms[ALRM_MAX]; + int alarmDelay; + + NetPath netPath; + + /*Usefull to init network stuff*/ + UInteger8 port_communication_technology; + + /*Stats header will be re-printed when set to true*/ + Boolean resetStatisticsLog; + + int listenCount; // number of consecutive resets to listening + int resetCount; + int announceTimeouts; + int current_init_clock; + int can_step_clock; + int warned_operator_slow_slewing; + int warned_operator_fast_slewing; + Boolean warnedUnicastCapacity; + int maxDelayRejected; + + Boolean runningBackupInterface; + + + char char_last_msg; /* representation of last message processed by servo */ + + int syncWaiting; /* we'll only start the delayReq timer after the first sync */ + int delayRespWaiting; /* Just for information purposes */ + Boolean startup_in_progress; + + Boolean pastStartup; /* we've set the clock already, at least once */ + + Boolean offsetFirstUpdated; + + uint32_t init_timestamp; /* When the clock was last initialised */ + Integer32 stabilisation_time; /* How long (seconds) it took to stabilise the clock */ + double last_saved_drift; /* Last observed drift value written to file */ + Boolean drift_saved; /* Did we save a drift value already? */ + + /* user description is max size + 1 to leave space for a null terminator */ + Octet userDescription[USER_DESCRIPTION_MAX + 1]; + Octet profileIdentity[6]; + + Integer32 lastSyncDst; /* destination address for last sync, so we know where to send the followUp - last resort: we should capture the dst address ourselves */ + Integer32 lastPdelayRespDst; /* captures the destination address of last pdelayResp so we know where to send the pdelayRespfollowUp */ + + /* + * counters - useful for debugging and monitoring, + * should be exposed through management messages + * and SNMP eventually + */ + PtpdCounters counters; + + /* PI servo model */ + PIservo servo; + + /* "panic mode" support */ + Boolean panicMode; /* in panic mode - do not update clock or calculate offsets */ + Boolean panicOver; /* panic mode is over, we can reset the clock */ + int panicModeTimeLeft; /* How many 30-second periods left in panic mode */ + + /* used to wait on failure while allowing timers to tick */ + Boolean initFailure; + Integer32 initFailureTimeout; + + /* + * used to inform TimingService about our status, but so + * that PTP code is independent from LibCCK and glue code can poll this + */ + ClockControlInfo clockControl; + ClockStatusInfo clockStatus; + + + TimeInternal rawDelayMS; + TimeInternal rawDelaySM; + TimeInternal rawPdelayMS; + TimeInternal rawPdelaySM; + +#ifdef PTPD_STATISTICS + /* + * basic clock statistics information, useful + * for monitoring servo performance and estimating + * clock stability - should be exposed through + * management messages and SNMP eventually + */ + PtpEngineSlaveStats slaveStats; + + OutlierFilter oFilterMS; + OutlierFilter oFilterSM; + + DoubleMovingStatFilter *filterMS; + DoubleMovingStatFilter *filterSM; + +#endif + + Integer32 acceptedUpdates; + Integer32 offsetUpdates; + + Boolean isCalibrated; + + NTPcontrol ntpControl; + + /* the interface to TimingDomain */ + TimingService timingService; + + /* accumulating offset correction added when smearing leap second */ + double leapSmearFudge; + + /* configuration applied by management messages */ + dictionary *managementConfig; + + /* testing only - used to add a 1ms offset */ +#if 0 + Boolean addOffset; +#endif + + RunTimeOpts *rtOpts; + +} PtpClock; + + +#endif /*DATATYPES_H_*/ diff --git a/src/ptpd/src/def/README b/src/ptpd/src/def/README new file mode 100644 index 0000000..a0ee02a --- /dev/null +++ b/src/ptpd/src/def/README @@ -0,0 +1,11 @@ +This directory contains component macro .def files that are referenced by X-Macros in +the ptpd source code. + +The component macros are used to define message, derived data +types, and management TLV fields. + +The X-Macros are used to automatically generate most of +the code used to pack, unpack, and free ptp data fields. + +Documentation on how to use X-Macros can be found at: +http://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros diff --git a/src/ptpd/src/def/derivedData/clockQuality.def b/src/ptpd/src/def/derivedData/clockQuality.def new file mode 100644 index 0000000..1ee37f3 --- /dev/null +++ b/src/ptpd/src/def/derivedData/clockQuality.def @@ -0,0 +1,8 @@ +/* Spec 5.3.7 ClockQuality */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( clockClass, 1, UInteger8 ) +OPERATE( clockAccuracy, 1, Enumeration8 ) +OPERATE( offsetScaledLogVariance, 2, UInteger16) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/faultRecord.def b/src/ptpd/src/def/derivedData/faultRecord.def new file mode 100644 index 0000000..172a79d --- /dev/null +++ b/src/ptpd/src/def/derivedData/faultRecord.def @@ -0,0 +1,11 @@ +/* Spec 5.3.10 FaultRecord */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( faultRecordLength, 2, UInteger16) +OPERATE( faultTime, 10, Timestamp) +OPERATE( severityCode, 1, Enumeration8) +OPERATE( faultName, 1 + data->faultName.lengthField, PTPText) +OPERATE( faultValue, 1 + data->faultValue.lengthField, PTPText) +OPERATE( faultDescription, 1 + data->faultDescription.lengthField, PTPText) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/physicalAddress.def b/src/ptpd/src/def/derivedData/physicalAddress.def new file mode 100644 index 0000000..3e73501 --- /dev/null +++ b/src/ptpd/src/def/derivedData/physicalAddress.def @@ -0,0 +1,13 @@ +/* PhysicalAddress */ + +/* + * This is not a derived data type from the standard. + * Defining this type simplifies the implementation of + * the Management Clock Description message + */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( addressLength, 2, UInteger16) +OPERATE( addressField, data->addressLength, Octet*) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/portAddress.def b/src/ptpd/src/def/derivedData/portAddress.def new file mode 100644 index 0000000..78d1fa2 --- /dev/null +++ b/src/ptpd/src/def/derivedData/portAddress.def @@ -0,0 +1,8 @@ +/* Spec 5.3.6 PortAddress */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( networkProtocol, 2, Enumeration16) +OPERATE( addressLength, 2, UInteger16) +OPERATE( addressField, data->addressLength, Octet*) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/portIdentity.def b/src/ptpd/src/def/derivedData/portIdentity.def new file mode 100644 index 0000000..a487dc9 --- /dev/null +++ b/src/ptpd/src/def/derivedData/portIdentity.def @@ -0,0 +1,7 @@ +/* Spec 5.3.5 PortIdentity */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( clockIdentity, CLOCK_IDENTITY_LENGTH, ClockIdentity) +OPERATE( portNumber, 2, UInteger16) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/ptpText.def b/src/ptpd/src/def/derivedData/ptpText.def new file mode 100644 index 0000000..6d60bd4 --- /dev/null +++ b/src/ptpd/src/def/derivedData/ptpText.def @@ -0,0 +1,7 @@ +/* Spec 5.3.9 PTPText */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( lengthField, 1, UInteger8) +OPERATE( textField, data->lengthField, Octet*) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/timeInterval.def b/src/ptpd/src/def/derivedData/timeInterval.def new file mode 100644 index 0000000..ac58749 --- /dev/null +++ b/src/ptpd/src/def/derivedData/timeInterval.def @@ -0,0 +1,6 @@ +/* Spec 5.3.2 TimeInterval*/ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( scaledNanoseconds, 8, Integer64) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/timePropertiesDS.def b/src/ptpd/src/def/derivedData/timePropertiesDS.def new file mode 100644 index 0000000..61215ca --- /dev/null +++ b/src/ptpd/src/def/derivedData/timePropertiesDS.def @@ -0,0 +1,13 @@ +/* Spec 8.2.4 timePropertiesDS */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( currentUtcOffset, 2, UInteger16 ) +OPERATE( currentUtcOffsetValid, 1, Boolean ) +OPERATE( leap59, 1, Boolean ) +OPERATE( leap61, 1, Boolean ) +OPERATE( timeTraceable, 1, Boolean ) +OPERATE( frequencyTraceable, 1, Boolean ) +OPERATE( ptpTimescale, 1, Boolean ) +OPERATE( timeSource, 1, Enumeration8 ) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/timestamp.def b/src/ptpd/src/def/derivedData/timestamp.def new file mode 100644 index 0000000..7d44db0 --- /dev/null +++ b/src/ptpd/src/def/derivedData/timestamp.def @@ -0,0 +1,7 @@ +/* Spec 5.3.3 Timestamp */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( secondsField, 6, UInteger48) +OPERATE( nanosecondsField, 4, UInteger32) + +#undef OPERATE diff --git a/src/ptpd/src/def/derivedData/tlv.def b/src/ptpd/src/def/derivedData/tlv.def new file mode 100644 index 0000000..6a52eb5 --- /dev/null +++ b/src/ptpd/src/def/derivedData/tlv.def @@ -0,0 +1,8 @@ +/* Spec 5.3.8 TLV */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( tlvType, 2, Enumeration16) +OPERATE( lengthField, 2, UInteger16) +OPERATE( valueField, data->lengthField, Octet*) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/announceReceiptTimeout.def b/src/ptpd/src/def/managementTLV/announceReceiptTimeout.def new file mode 100644 index 0000000..8a62653 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/announceReceiptTimeout.def @@ -0,0 +1,7 @@ +/* Spec Table 63 - ANNOUNCE_RECEIPT_TIMEOUT management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( announceReceiptTimeout, 1, UInteger8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/clockAccuracy.def b/src/ptpd/src/def/managementTLV/clockAccuracy.def new file mode 100644 index 0000000..a8e32bb --- /dev/null +++ b/src/ptpd/src/def/managementTLV/clockAccuracy.def @@ -0,0 +1,7 @@ +/* Spec Table 49 - CLOCK_ACCURACY management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( clockAccuracy, 1, Enumeration8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/clockDescription.def b/src/ptpd/src/def/managementTLV/clockDescription.def new file mode 100644 index 0000000..645d9cd --- /dev/null +++ b/src/ptpd/src/def/managementTLV/clockDescription.def @@ -0,0 +1,35 @@ +/* Spec Table 41 - CLOCK_DESCRIPTION management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( clockType0, 1, Octet) +OPERATE( clockType1, 1, Octet) +OPERATE( physicalLayerProtocol, + 1 + data->physicalLayerProtocol.lengthField, + PTPText) +OPERATE( physicalAddress, + 2 + data->physicalAddress.addressLength, + PhysicalAddress) +OPERATE( protocolAddress, + 4 + data->protocolAddress.addressLength, + PortAddress) +OPERATE( manufacturerIdentity0, 1, Octet) +OPERATE( manufacturerIdentity1, 1, Octet) +OPERATE( manufacturerIdentity2, 1, Octet) +OPERATE( reserved, 1, Octet) +OPERATE( productDescription, + 1 + data->productDescription.lengthField, + PTPText) +OPERATE( revisionData, + 1 + data->revisionData.lengthField, + PTPText) +OPERATE( userDescription, + 1 + data->userDescription.lengthField, + PTPText) +OPERATE( profileIdentity0, 1, Octet) +OPERATE( profileIdentity1, 1, Octet) +OPERATE( profileIdentity2, 1, Octet) +OPERATE( profileIdentity3, 1, Octet) +OPERATE( profileIdentity4, 1, Octet) +OPERATE( profileIdentity5, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/currentDataSet.def b/src/ptpd/src/def/managementTLV/currentDataSet.def new file mode 100644 index 0000000..b478646 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/currentDataSet.def @@ -0,0 +1,8 @@ +/* Spec Table 55 - CURRENT_DATA_SET management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( stepsRemoved, 2, UInteger16) +OPERATE( offsetFromMaster, 8, TimeInterval) +OPERATE( meanPathDelay, 8, TimeInterval) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/defaultDataSet.def b/src/ptpd/src/def/managementTLV/defaultDataSet.def new file mode 100644 index 0000000..6f3d24f --- /dev/null +++ b/src/ptpd/src/def/managementTLV/defaultDataSet.def @@ -0,0 +1,14 @@ +/* Spec Table 50 - DEFAULT_DATA_SET management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( so_tsc, 1, Octet) +OPERATE( reserved0, 1, Octet) +OPERATE( numberPorts, 2, UInteger16) +OPERATE( priority1, 1, UInteger8) +OPERATE( clockQuality, 4, ClockQuality) +OPERATE( priority2, 1, UInteger8) +OPERATE( clockIdentity, 8, ClockIdentity) +OPERATE( domainNumber, 1, UInteger8) +OPERATE( reserved1, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/delayMechanism.def b/src/ptpd/src/def/managementTLV/delayMechanism.def new file mode 100644 index 0000000..edcda96 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/delayMechanism.def @@ -0,0 +1,7 @@ +/* Spec Table 65 - DELAY_MECHANISM management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( delayMechanism, 1, Enumeration8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/domain.def b/src/ptpd/src/def/managementTLV/domain.def new file mode 100644 index 0000000..6a15333 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/domain.def @@ -0,0 +1,7 @@ +/* Spec Table 53 - DOMAIN management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( domainNumber, 1, UInteger8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/errorStatus.def b/src/ptpd/src/def/managementTLV/errorStatus.def new file mode 100644 index 0000000..303b240 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/errorStatus.def @@ -0,0 +1,8 @@ +/* Spec Table 71 - MANAGEMENT_ERROR_STATUS TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( managementId, 2, Enumeration16) +OPERATE( reserved, 4, UInteger32) +OPERATE( displayData, 1 + data->displayData.lengthField, PTPText) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/initialize.def b/src/ptpd/src/def/managementTLV/initialize.def new file mode 100644 index 0000000..d6fbf1c --- /dev/null +++ b/src/ptpd/src/def/managementTLV/initialize.def @@ -0,0 +1,6 @@ +/* Spec Table 43 - USER_DESCRIPTION management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( initializeKey, 2, UInteger16) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/logAnnounceInterval.def b/src/ptpd/src/def/managementTLV/logAnnounceInterval.def new file mode 100644 index 0000000..1fc30dd --- /dev/null +++ b/src/ptpd/src/def/managementTLV/logAnnounceInterval.def @@ -0,0 +1,7 @@ +/* Spec Table 62 - LOG_ANNOUNCE_INTERVAL management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( logAnnounceInterval, 1, Integer8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/logMinPdelayReqInterval.def b/src/ptpd/src/def/managementTLV/logMinPdelayReqInterval.def new file mode 100644 index 0000000..c382d7d --- /dev/null +++ b/src/ptpd/src/def/managementTLV/logMinPdelayReqInterval.def @@ -0,0 +1,7 @@ +/* Spec Table 66 - LOG_MIN_PDELAY_REQ_INTERVAL management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( logMinPdelayReqInterval, 1, Integer8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/logSyncInterval.def b/src/ptpd/src/def/managementTLV/logSyncInterval.def new file mode 100644 index 0000000..3700ddb --- /dev/null +++ b/src/ptpd/src/def/managementTLV/logSyncInterval.def @@ -0,0 +1,7 @@ +/* Spec Table 64 - LOG_SYNC_INTERVAL management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( logSyncInterval, 1, Integer8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/managementTLV.def b/src/ptpd/src/def/managementTLV/managementTLV.def new file mode 100644 index 0000000..9d51de8 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/managementTLV.def @@ -0,0 +1,8 @@ +/* Spec Table 39 - Management TLV fields */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( tlvType, 2, Enumeration16) +OPERATE( lengthField, 2, UInteger16) +OPERATE( managementId, 2, Enumeration16) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/parentDataSet.def b/src/ptpd/src/def/managementTLV/parentDataSet.def new file mode 100644 index 0000000..9440d4a --- /dev/null +++ b/src/ptpd/src/def/managementTLV/parentDataSet.def @@ -0,0 +1,14 @@ +/* Spec Table 56 - PARENT_DATA_SET management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( parentPortIdentity, 10, PortIdentity) +OPERATE( PS, 1, Boolean) +OPERATE( reserved, 1, Octet) +OPERATE( observedParentOffsetScaledLogVariance, 2, UInteger16) +OPERATE( observedParentClockPhaseChangeRate, 4, Integer32) +OPERATE( grandmasterPriority1, 1, UInteger8) +OPERATE( grandmasterClockQuality, 4, ClockQuality) +OPERATE( grandmasterPriority2, 1, UInteger8) +OPERATE( grandmasterIdentity, 8, ClockIdentity) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/portDataSet.def b/src/ptpd/src/def/managementTLV/portDataSet.def new file mode 100644 index 0000000..2fcce7f --- /dev/null +++ b/src/ptpd/src/def/managementTLV/portDataSet.def @@ -0,0 +1,16 @@ +/* Spec Table 61 - PORT_DATA_SET management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( portIdentity, 10, PortIdentity) +OPERATE( portState, 1, Enumeration8) +OPERATE( logMinDelayReqInterval, 1, Integer8) +OPERATE( peerMeanPathDelay, 8, TimeInterval) +OPERATE( logAnnounceInterval, 1, Integer8) +OPERATE( announceReceiptTimeout, 1, UInteger8) +OPERATE( logSyncInterval, 1, Integer8) +OPERATE( delayMechanism, 1, Enumeration8) +OPERATE( logMinPdelayReqInterval, 1, Integer8) +OPERATE( reserved, 0, NibbleUpper) +OPERATE( versionNumber, 1, UInteger4Lower) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/priority1.def b/src/ptpd/src/def/managementTLV/priority1.def new file mode 100644 index 0000000..fa5e6fa --- /dev/null +++ b/src/ptpd/src/def/managementTLV/priority1.def @@ -0,0 +1,7 @@ +/* Spec Table 51 - PRIORITY1 management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( priority1, 1, UInteger8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/priority2.def b/src/ptpd/src/def/managementTLV/priority2.def new file mode 100644 index 0000000..8e72aae --- /dev/null +++ b/src/ptpd/src/def/managementTLV/priority2.def @@ -0,0 +1,7 @@ +/* Spec Table 52 - PRIORITY2 management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( priority2, 1, UInteger8) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/slaveOnly.def b/src/ptpd/src/def/managementTLV/slaveOnly.def new file mode 100644 index 0000000..65fa4a6 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/slaveOnly.def @@ -0,0 +1,7 @@ +/* Spec Table 54 - SLAVE_ONLY management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( so, 1, Boolean) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/time.def b/src/ptpd/src/def/managementTLV/time.def new file mode 100644 index 0000000..485b083 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/time.def @@ -0,0 +1,6 @@ +/* Spec Table 48 - TIME management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( currentTime, 10, Timestamp) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/timePropertiesDataSet.def b/src/ptpd/src/def/managementTLV/timePropertiesDataSet.def new file mode 100644 index 0000000..50f6eaf --- /dev/null +++ b/src/ptpd/src/def/managementTLV/timePropertiesDataSet.def @@ -0,0 +1,8 @@ +/* Spec Table 57 - TIME_PROPERTIES_DATA_SET management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( currentUtcOffset, 2, Integer16) +OPERATE( ftra_ttra_ptp_utcv_li59_li61, 1, Octet) +OPERATE( timeSource, 1, Enumeration8) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/timescaleProperties.def b/src/ptpd/src/def/managementTLV/timescaleProperties.def new file mode 100644 index 0000000..40de808 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/timescaleProperties.def @@ -0,0 +1,7 @@ +/* Spec Table 60 - TIMESCALE_PROPERTIES management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( ptp, 1, Boolean ) +OPERATE( timeSource, 1, Enumeration8 ) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/traceabilityProperties.def b/src/ptpd/src/def/managementTLV/traceabilityProperties.def new file mode 100644 index 0000000..5e5f9c0 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/traceabilityProperties.def @@ -0,0 +1,7 @@ +/* Spec Table 59 - TRACEABILITY_PROPERTIES management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( ftra_ttra, 1, Octet) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/unicastNegotiationEnable.def b/src/ptpd/src/def/managementTLV/unicastNegotiationEnable.def new file mode 100644 index 0000000..2b3034b --- /dev/null +++ b/src/ptpd/src/def/managementTLV/unicastNegotiationEnable.def @@ -0,0 +1,7 @@ +/* Spec Table 77 - UNICAST_NEGOTIATION_ENABLE */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( en , 1, Boolean) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/userDescription.def b/src/ptpd/src/def/managementTLV/userDescription.def new file mode 100644 index 0000000..db3e8d9 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/userDescription.def @@ -0,0 +1,8 @@ +/* Spec Table 43 - USER_DESCRIPTION management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( userDescription, + 1 + data->userDescription.lengthField, + PTPText) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/utcProperties.def b/src/ptpd/src/def/managementTLV/utcProperties.def new file mode 100644 index 0000000..644cb93 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/utcProperties.def @@ -0,0 +1,8 @@ +/* Spec Table 58 - UTC_PROPERTIES management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( currentUtcOffset, 2, Integer16) +OPERATE( utcv_li59_li61, 1, Octet) +OPERATE( reserved, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/managementTLV/versionNumber.def b/src/ptpd/src/def/managementTLV/versionNumber.def new file mode 100644 index 0000000..9b8fa28 --- /dev/null +++ b/src/ptpd/src/def/managementTLV/versionNumber.def @@ -0,0 +1,8 @@ +/* Spec Table 67 - VERSION_NUMBER management TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( reserved0, 0, NibbleUpper) +OPERATE( versionNumber, 1, UInteger4Lower) +OPERATE( reserved1, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/message/header.def b/src/ptpd/src/def/message/header.def new file mode 100644 index 0000000..8e51d63 --- /dev/null +++ b/src/ptpd/src/def/message/header.def @@ -0,0 +1,20 @@ +/* Spec Table 18 - Common message header */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( transportSpecific, 0, NibbleUpper) +OPERATE( messageType, 1, Enumeration4Lower) +OPERATE( reserved0, 0, NibbleUpper) +OPERATE( versionPTP, 1, UInteger4Lower) +OPERATE( messageLength, 2, UInteger16) +OPERATE( domainNumber, 1, UInteger8) +OPERATE( reserved1, 1, Octet) +OPERATE( flagField0, 1, Octet) +OPERATE( flagField1, 1, Octet) +OPERATE( correctionField, 8, Integer64) +OPERATE( reserved2, 4, UInteger32) +OPERATE( sourcePortIdentity, 10, PortIdentity) +OPERATE( sequenceId, 2, UInteger16) +OPERATE( controlField, 1, UInteger8) +OPERATE( logMessageInterval, 1, Integer8) + +#undef OPERATE diff --git a/src/ptpd/src/def/message/management.def b/src/ptpd/src/def/message/management.def new file mode 100644 index 0000000..350498f --- /dev/null +++ b/src/ptpd/src/def/message/management.def @@ -0,0 +1,12 @@ +/* Spec Table 37 - Management message fields */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( header, 34, MsgHeader) +OPERATE( targetPortIdentity, 10, PortIdentity) +OPERATE( startingBoundaryHops, 1, UInteger8) +OPERATE( boundaryHops, 1, UInteger8) +OPERATE( reserved0, 0, NibbleUpper) +OPERATE( actionField, 1, Enumeration4Lower) +OPERATE( reserved1, 1, Octet) + +#undef OPERATE diff --git a/src/ptpd/src/def/message/signaling.def b/src/ptpd/src/def/message/signaling.def new file mode 100644 index 0000000..e61c82c --- /dev/null +++ b/src/ptpd/src/def/message/signaling.def @@ -0,0 +1,6 @@ +/* Spec Table 33 - Signaling message fields */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( header, 34, MsgHeader) +OPERATE( targetPortIdentity, 10, PortIdentity) +#undef OPERATE diff --git a/src/ptpd/src/def/signalingTLV/acknowledgeCancelUnicastTransmission.def b/src/ptpd/src/def/signalingTLV/acknowledgeCancelUnicastTransmission.def new file mode 100644 index 0000000..abff8cc --- /dev/null +++ b/src/ptpd/src/def/signalingTLV/acknowledgeCancelUnicastTransmission.def @@ -0,0 +1,9 @@ +/* Spec Table 75 - ACKNOWLEDGE_UNICAST_TRANSMISSION signaling TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ + +OPERATE( messageType, 0, Enumeration4Upper) +OPERATE( reserved0, 1, UInteger4Lower) +OPERATE( reserved1, 1, Octet ) + +#undef OPERATE diff --git a/src/ptpd/src/def/signalingTLV/cancelUnicastTransmission.def b/src/ptpd/src/def/signalingTLV/cancelUnicastTransmission.def new file mode 100644 index 0000000..2e53914 --- /dev/null +++ b/src/ptpd/src/def/signalingTLV/cancelUnicastTransmission.def @@ -0,0 +1,9 @@ +/* Spec Table 75 - CANCEL_UNICAST_TRANSMISSION signaling TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ + +OPERATE( messageType, 0, Enumeration4Upper) +OPERATE( reserved0, 1, UInteger4Lower) +OPERATE( reserved1, 1, Octet ) + +#undef OPERATE diff --git a/src/ptpd/src/def/signalingTLV/grantUnicastTransmission.def b/src/ptpd/src/def/signalingTLV/grantUnicastTransmission.def new file mode 100644 index 0000000..8677d60 --- /dev/null +++ b/src/ptpd/src/def/signalingTLV/grantUnicastTransmission.def @@ -0,0 +1,12 @@ +/* Spec Table 73 - REQUEST_UNICAST_TRANSMISSION signaling TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ + +OPERATE( messageType, 0, Enumeration4Upper) +OPERATE( reserved0, 1, UInteger4Lower) +OPERATE( logInterMessagePeriod, 1, Integer8) +OPERATE( durationField, 4, UInteger32) +OPERATE( reserved1, 1, Octet ) +OPERATE( renewal_invited,1, UInteger8) + +#undef OPERATE diff --git a/src/ptpd/src/def/signalingTLV/requestUnicastTransmission.def b/src/ptpd/src/def/signalingTLV/requestUnicastTransmission.def new file mode 100644 index 0000000..8a57cb2 --- /dev/null +++ b/src/ptpd/src/def/signalingTLV/requestUnicastTransmission.def @@ -0,0 +1,9 @@ +/* Spec Table 73 - REQUEST_UNICAST_TRANSMISSION signaling TLV data field */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( messageType, 0, Enumeration4Upper) +OPERATE( reserved0, 1, UInteger4Lower) +OPERATE( logInterMessagePeriod, 1, Integer8) +OPERATE( durationField, 4, UInteger32) + +#undef OPERATE diff --git a/src/ptpd/src/def/signalingTLV/signalingTLV.def b/src/ptpd/src/def/signalingTLV/signalingTLV.def new file mode 100644 index 0000000..de662fd --- /dev/null +++ b/src/ptpd/src/def/signalingTLV/signalingTLV.def @@ -0,0 +1,6 @@ +/* 4.1 - TLV fields */ + +/* to use these definitions, #define OPERATE then #include this file in your source */ +OPERATE( tlvType, 2, Enumeration16) +OPERATE( lengthField, 2, UInteger16) +#undef OPERATE diff --git a/src/ptpd/src/dep/alarms.c b/src/ptpd/src/dep/alarms.c new file mode 100644 index 0000000..d1437a6 --- /dev/null +++ b/src/ptpd/src/dep/alarms.c @@ -0,0 +1,427 @@ +/*- + * Copyright (c) 2015 Wojciech Owczarek + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file alarms.c + * @authors Wojciech Owczarek + * @date Wed Dec 9 19:13:10 2015 + * This source file contains the implementations of functions + * handling raising and clearing of alarms. + */ + +#include "../ptpd.h" + +static const char* alarmStateToString(AlarmState state); + +static void dispatchEvent(AlarmEntry *alarm); +static void dispatchAlarm(AlarmEntry *alarm); + +static void getAlarmMessage(char *out, int count, AlarmEntry *alarm); + +static void alarmHandler_log(AlarmEntry *alarm); +static void eventHandler_log(AlarmEntry *alarm); + +static const char +*alarmStateToString(AlarmState state) +{ + + switch(state) { + + case ALARM_UNSET: + return "NONE"; + case ALARM_SET: + return "SET"; + case ALARM_CLEARED: + return "CLEAR"; + default: + return "?"; + } + +} + +static void +getAlarmMessage(char *out, int count, AlarmEntry *alarm) +{ + + memset(out, 0, count); + + switch(alarm->id) { + + case ALRM_PORT_STATE: + if(alarm->state == ALARM_UNSET) { + return; + } + snprintf(out, count, ": Port state is %s", portState_getName(alarm->eventData.portDS.portState)); + return; + case ALRM_OFM_THRESHOLD: + if(alarm->state == ALARM_UNSET) { + snprintf(out, count, ": Offset from master is now %.09f s, threshold is %d ns", + timeInternalToDouble(&alarm->eventData.currentDS.offsetFromMaster), + alarm->eventData.ofmAlarmThreshold); + return; + } + snprintf(out, count, ": Offset from master is %.09f s, threshold is %d ns", + timeInternalToDouble(&alarm->eventData.currentDS.offsetFromMaster), + alarm->eventData.ofmAlarmThreshold); + return; + case ALRM_OFM_SECONDS: + if(alarm->state == ALARM_UNSET) { + snprintf(out, count, ": Offset from master is now %.09f s, below 1 second", + timeInternalToDouble(&alarm->eventData.currentDS.offsetFromMaster)); + return; + } + snprintf(out, count, ": Offset from master is %.09f s, above 1 second", + timeInternalToDouble(&alarm->eventData.currentDS.offsetFromMaster)); + return; + case ALRM_CLOCK_STEP: + snprintf(out, count, ": Clock stepped by %.09f s", + timeInternalToDouble(&alarm->eventData.currentDS.offsetFromMaster)); + return; + case ALRM_NO_SYNC: + if(alarm->state == ALARM_UNSET) { + return; + } + snprintf(out, count, ": Not receiving Sync"); + return; + case ALRM_NO_DELAY: + if(alarm->state == ALARM_UNSET) { + return; + } + snprintf(out, count, ": Not receiving Delay Response"); + return; + case ALRM_MASTER_CHANGE: + return; + case ALRM_NETWORK_FLT: + return; + case ALRM_FAST_ADJ: + return; + case ALRM_TIMEPROP_CHANGE: + return; + case ALRM_DOMAIN_MISMATCH: + snprintf(out, count, ": Configured domain is %d, last seen %d", alarm->eventData.defaultDS.domainNumber, + alarm->eventData.portDS.lastMismatchedDomain); + return; + default: + return; + } + +} + +static void +alarmHandler_log(AlarmEntry *alarm) +{ + + char message[ALARM_MESSAGE_LENGTH+1]; + message[ALARM_MESSAGE_LENGTH] = '\0'; + getAlarmMessage(message, ALARM_MESSAGE_LENGTH, alarm); + + if(alarm->state == ALARM_SET) { + NOTICE("Alarm %s set%s\n", alarm->name, message); + } + + if(alarm->state == ALARM_UNSET) { + NOTICE("Alarm %s cleared%s\n", alarm->name, message); + } +} + +static void +eventHandler_log(AlarmEntry *alarm) +{ + char message[ALARM_MESSAGE_LENGTH+1]; + message[ALARM_MESSAGE_LENGTH] = '\0'; + getAlarmMessage(message, ALARM_MESSAGE_LENGTH, alarm); + + NOTICE("Event %s triggered%s\n", alarm->name, message); +} + +static void +dispatchEvent(AlarmEntry *alarm) { + + for(int i=0; alarm->handlers[i] != NULL; i++) { + alarm->handlers[i](alarm); + } + alarm->condition = FALSE; +} + +static void +dispatchAlarm(AlarmEntry *alarm) +{ + for(int i=0; alarm->handlers[i] != NULL; i++) { + alarm->handlers[i](alarm); + } +} + + +void +initAlarms(AlarmEntry* alarms, int count, void *userData) +{ + + static AlarmEntry alarmTemplate[] = { + + /* short */ /* name */ /* desc */ /* enabled? */ /* id */ /* eventOnly */ /*handlers */ + { "STA", "PORT_STATE", "Port state different to expected value", FALSE, ALRM_PORT_STATE, FALSE, {alarmHandler_log}}, + { "OFM", "OFM_THRESHOLD", "Offset From Master outside threshold", FALSE, ALRM_OFM_THRESHOLD, FALSE, {alarmHandler_log}}, + { "OFMS", "OFM_SECONDS", "Offset From Master above 1 second", FALSE, ALRM_OFM_SECONDS, FALSE, {alarmHandler_log}}, + { "STEP", "CLOCK_STEP", "Clock was stepped", FALSE, ALRM_CLOCK_STEP, TRUE, {eventHandler_log}}, + { "SYN", "NO_SYNC", "Clock is not receiving Sync messages", FALSE, ALRM_NO_SYNC, FALSE, {alarmHandler_log}}, + { "DLY", "NO_DELAY", "Clock is not receiving (p)Delay responses", FALSE, ALRM_NO_DELAY, FALSE, {alarmHandler_log}}, + { "MSTC", "MASTER_CHANGE", "Best master has changed", FALSE, ALRM_MASTER_CHANGE, TRUE, {eventHandler_log}}, + { "NWFL", "NETWORK_FAULT", "A network fault has occurred", FALSE, ALRM_NETWORK_FLT, FALSE, {alarmHandler_log}}, + { "FADJ", "FAST_ADJ", "Clock is being adjusted too fast", FALSE, ALRM_FAST_ADJ, FALSE, {alarmHandler_log}}, + { "TPR", "TIMEPROP_CHANGE", "Time properties have changed", FALSE, ALRM_TIMEPROP_CHANGE, TRUE, {eventHandler_log}}, + { "DOM", "DOMAIN_MISMATCH", "Clock is receiving all messages from incorrect domain",FALSE, ALRM_DOMAIN_MISMATCH, FALSE, {alarmHandler_log}} + + }; + + if(count > ALRM_MAX) return; + + memset(alarms, 0, count * sizeof(AlarmEntry)); + memcpy(alarms, alarmTemplate, sizeof(alarmTemplate)); + + for(int i = 0; i < count; i++) { + alarms[i].userData = userData; + } + +} + +/* + * we are passing a generic pointer because eventually the alarm will have a "source" field, + * that being PTP, NTP or any other subsystem, which may have separate routines to handle this + */ +void +configureAlarms(AlarmEntry *alarms, int count, void * userData) +{ + + PtpClock *ptpClock = (PtpClock*) userData; + + for(int i = 0; i < count; i++) { + alarms[i].minAge = ptpClock->rtOpts->alarmMinAge; + alarms[i].enabled = ptpClock->rtOpts->alarmsEnabled; +#ifdef PTPD_SNMP + if(ptpClock->rtOpts->snmpEnabled && ptpClock->rtOpts->snmpTrapsEnabled) { + DBG("SNMP alarm handler attached for %s\n", alarms[i].name); + alarms[i].handlers[1] = alarmHandler_snmp; + } else { + alarms[i].handlers[1] = NULL; + } +#endif + } + +} + +void +enableAlarms(AlarmEntry* alarms, int count, Boolean enabled) +{ + for(int i = 0; i < count; i++) { + alarms[i].enabled = enabled; + } + +} + +void +setAlarmCondition(AlarmEntry *alarm, Boolean condition, PtpClock *ptpClock) +{ + if(!alarm->enabled) { + return; + } + + Boolean change = condition ^ alarm->condition; + + /* if there is no change, exit */ + if(!change) { + return; + } + + /* condition has cleared but event has not yet been processed - don't touch it */ + if(!condition && alarm->unhandled) { + return; + } + + /* capture event data and time if condition is met */ + + capturePtpEventData(&alarm->eventData, ptpClock, ptpClock->rtOpts); + + if(condition) { + getTime(&alarm->timeSet); + } else { + getTime(&alarm->timeCleared); + } + + DBG("Alarm %s condition set to %s\n", alarm->name, condition ? "TRUE" : "FALSE"); + + alarm->age = 0; + alarm->condition = condition; + alarm->unhandled = TRUE; + +} + +void +capturePtpEventData(PtpEventData *eventData, PtpClock *ptpClock, RunTimeOpts *rtOpts) +{ + + eventData->defaultDS = ptpClock->defaultDS; + eventData->currentDS = ptpClock->currentDS; + eventData->timePropertiesDS = ptpClock->timePropertiesDS; + eventData->portDS = ptpClock->portDS; + eventData->parentDS = ptpClock->parentDS; + + if(ptpClock->bestMaster != NULL) { + eventData->bestMaster = *ptpClock->bestMaster; + } else { + memset(&eventData->bestMaster, 0, sizeof(ForeignMasterRecord)); + } + + eventData->ofmAlarmThreshold = rtOpts->ofmAlarmThreshold; +} + +/* + * this is our asynchronous event/alarm processor / state machine. ALARM_TIMEOUT_PERIOD protects us from alarms flapping, + * and ensures that an alarm will last at least n seconds: notification is processed only when the alarm is set for the + * first time (from UNSET state), and is not cancelled until the timeout passes (in the meantime, alarm condition can be + * triggered again and timer is reset, so it can flap at will), and cancel notification will only be sent when the condition + * has cleared and timeout has passed. + */ +void +updateAlarms(AlarmEntry *alarms, int count) +{ + + DBG("updateAlarms()\n"); + + AlarmEntry *alarm; + AlarmState lastState; + + for(int i = 0; i < count; i++) { + + alarm = &alarms[i]; + + if(!alarm->enabled) { + continue; + } + + lastState = alarm->state; + /* this is a one-off event */ + if(alarm->eventOnly) { + if(alarm->condition) { + dispatchEvent(alarm); + } + } else { + /* this is an alarm */ + if(!alarm->condition) { + /* condition is false, alarm is cleared and aged out: unset */ + if(alarm->state == ALARM_CLEARED && alarm->age >= alarm->minAge ) { + alarm->state = ALARM_UNSET; + /* inform, run handlers */ + dispatchAlarm(alarm); + clearTime(&alarm->timeSet); + clearTime(&alarm->timeCleared); + /* condition is false and alarm was set - clear and wait for age out */ + } else if (alarm->state == ALARM_SET) { + alarm->state = ALARM_CLEARED; + } + /* condition is true and age is 0: condition just changed */ + } else if (alarm->age == 0) { + alarm->state = ALARM_SET; + /* react only if alarm was set from unset (don't inform multiple times until it fully clears */ + if(lastState == ALARM_UNSET) { + /* inform, run handlers */ + dispatchAlarm(alarm); + } + } + alarm->age+=ALARM_UPDATE_INTERVAL; + } + alarm->unhandled = FALSE; + } + +} + +void +displayAlarms(AlarmEntry *alarms, int count) +{ + + INFO("----------------------------------------------\n"); + INFO(" Alarm status: \n"); + INFO("----------------------------------------------\n"); + INFO("ALARM NAME\t| STATE | DESCRIPTION\n"); + INFO("----------------------------------------------\n"); + for(int i=0; i < count; i++) { + if(alarms[i].eventOnly) continue; + INFO("%-15s\t| %-8s | %s\n", alarms[i].name, + alarmStateToString(alarms[i].state), + alarms[i].description); + } + INFO("----------------------------------------------\n"); + +} + +/* populate @output with a one-line alarm summary, consisting of multiple: + * ALCD[x] + * where: + * - ALCD is the short name(code) + * - x is "!" when alarm is set + * - x is "." when alarm is cleared + * - when alarm is gone (unset) its code is not listed + * + * Returning the number of bytes written. When output is null, only the number of bytes needed is returned + * (e.g. to allow us to allocate a buffer) + */ +int +getAlarmSummary(char * output, int size, AlarmEntry *alarms, int count) +{ + + int len = 0; + int i = 0; + + char item[9]; + + AlarmEntry *alarm; + + if(output != NULL) { + memset(output, 0, size); + } + + for(i=0; i < count; i++) { + memset(item, 0, 8); + alarm = &alarms[i]; + + if(alarm->state == ALARM_UNSET) { + continue; + } + + strncpy(item, alarm->shortName, 4); + snprintf(item + strlen(item), 5, "[%s] ", alarm->state == ALARM_SET ? "!" : "."); + + if(output != NULL) { + strncat(output, item, size - len); + } + + len += strlen(item) + 1; + + } + + return len + 1; + +} diff --git a/src/ptpd/src/dep/alarms.h b/src/ptpd/src/dep/alarms.h new file mode 100644 index 0000000..0a1d3f1 --- /dev/null +++ b/src/ptpd/src/dep/alarms.h @@ -0,0 +1,100 @@ +#ifndef PTPDALARMS_H_ +#define PTPDALARMS_H_ + +/*- + * Copyright (c) 2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file alarms.h + * @authors Wojciech Owczarek + * @date Wed Dec 9 19:13:10 2015 + * Data type and function definitions related to + * handling raising and clearing alarms. + */ + +#include "../datatypes.h" + +#define DOMAIN_MISMATCH_MIN 10 /* trigger domain mismatch alarm after at least 10 mismatches */ +#define ALARM_UPDATE_INTERVAL 1 /* how often we process alarms */ +#define ALARM_TIMEOUT_PERIOD 30 /* minimal alarm age to clear */ +#define ALARM_HANDLERS_MAX 3 /* max number of alarm handlers */ +#define ALARM_MESSAGE_LENGTH 100/* length of an alarm-specific message string */ + +/* explicitly numbered because these are referenced in the MIB as textual convention */ +typedef enum { + ALRM_PORT_STATE = 0, /*x done*/ + ALRM_OFM_THRESHOLD = 1, /*x done */ + ALRM_OFM_SECONDS = 2, /*x done*/ + ALRM_CLOCK_STEP = 3, /*x done*/ + ALRM_NO_SYNC = 4, /*x done*/ + ALRM_NO_DELAY = 5, /*x done*/ + ALRM_MASTER_CHANGE = 6, /*x done*/ + ALRM_NETWORK_FLT = 7, /*x done*/ + ALRM_FAST_ADJ = 8, /*+/- currently only at maxppb */ + ALRM_TIMEPROP_CHANGE = 9, /*x done*/ + ALRM_DOMAIN_MISMATCH = 10, /*+/- currently only when all packets come from an incorrect domain */ + ALRM_MAX +} AlarmType; + +/* explicitly numbered because these are referenced in the MIB as textual convention */ +typedef enum { + ALARM_UNSET = 0, /* idle */ + ALARM_SET = 1, /* condition has been triggerd */ + ALARM_CLEARED = 2 /* condition has cleared */ +} AlarmState; + +struct _alarmEntry { + char shortName[5]; /* short code i.e. OFS, DLY, SYN, FLT etc. */ + char name[31]; /* full name i.e. OFFSET_THRESHOLD, NO_DELAY, NO_SYNC etc. */ + char description[101]; /* text description */ + Boolean enabled; /* is the alarm operational ? */ + uint8_t id; /* alarm ID */ + Boolean eventOnly; /* this is only an event - don't manage state, just dispatch/inform when condition is met */ + void (*handlers[ALARM_HANDLERS_MAX])(struct _alarmEntry *); /* alarm handlers */ + void * userData; /* user data pointer */ + Boolean unhandled; /* this event is pending pick-up - prevets from clearing condition until it's handled */ + uint32_t age; /* age of alarm in current state (seconds) */ + uint32_t minAge; /* minimum age of alarm (time between set and clear notification - condition can go away earlier */ + AlarmState state; /* state of the alarm */ + Boolean condition; /* is the alarm condition met? (so we can check conditions and set alarms separately */ + TimeInternal timeSet; /* time when set */ + TimeInternal timeCleared; /* time when cleared */ + Boolean internalOnly; /* do not display in status file / indicate that the alarm is internal only */ + PtpEventData eventData; /* the event data union - so we can capture any data we need without the need to capture a whole PtpClock */ +}; + +typedef struct _alarmEntry AlarmEntry; + +void initAlarms(AlarmEntry* alarms, int count, void* userData); /* fill an array with initial alarm data */ +void configureAlarms(AlarmEntry* alarms, int count, void* userData); /* fill an array with initial alarm data */ +void enableAlarms(AlarmEntry* alarms, int count, Boolean enabled); /* enable/disable all alarms */ +void updateAlarms(AlarmEntry *alarms, int count); /* dispatch alarms: age, set, clear alarms etc. */ +void displayAlarms(AlarmEntry *alarms, int count); /* display a formatted alarm summary table */ +int getAlarmSummary(char * output, int size, AlarmEntry *alarms, int count); /* produce a one-line alarm summary string */ +void handleAlarm(AlarmEntry *alarms, void *userData); + +#endif /*PTPDALARMS_H_*/ diff --git a/src/ptpd/src/dep/configdefaults.c b/src/ptpd/src/dep/configdefaults.c new file mode 100644 index 0000000..cdf0685 --- /dev/null +++ b/src/ptpd/src/dep/configdefaults.c @@ -0,0 +1,702 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +/* Copyright (c) 2013-2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file configtemplates.c + * @date Mon Oct 12 03:25:32 2015 + * + * @brief Definitions of configuration templates + * + */ + +#include "../ptpd.h" + +#define CONFIG_ISTRUE(key) \ + (iniparser_getboolean(dict,key,FALSE)==TRUE) + +#define CONFIG_ISSET(key) \ + (strcmp(iniparser_getstring(dict, key, ""),"") != 0) + +#define IS_QUIET()\ + ( CONFIG_ISTRUE("%quiet%:%quiet%") ) + +/* + * static definition of configuration templates - maximum 100 settings per template, + * as defined in configtemplates.h. Template for a template below. + */ + +/* =============== configuration templates begin ===============*/ + +static const ConfigTemplate configTemplates[] = { + + { "g8265-e2e-master", "ITU-T G.8265.1 (Telecom profile) unicast master", { + {"ptpengine:preset", "masteronly"}, + {"ptpengine:ip_mode", "unicast"}, + {"ptpengine:unicast_negotiation", "y"}, + {"ptpengine:domain", "4"}, + {"ptpengine:disable_bmca", "y"}, + {"ptpengine:delay_mechanism", "E2E"}, + {"ptpengine:log_sync_interval", "-6"}, + {"ptpengine:log_delayreq_interval", "-6"}, + {"ptpengine:announce_receipt_timeout", "3"}, + {"ptpengine:priority1", "128"}, + {"ptpengine:priority2", "128"}, + {NULL}} + }, + + { "g8265-e2e-slave", "ITU-T G.8265.1 (Telecom profile) unicast slave", { + {"ptpengine:preset", "slaveonly"}, + {"ptpengine:ip_mode", "unicast"}, + {"ptpengine:unicast_negotiation", "y"}, + {"ptpengine:domain", "4"}, + {"ptpengine:delay_mechanism", "E2E"}, + {"ptpengine:announce_receipt_timeout", "3"}, + {NULL}} + }, + + { "layer2-p2p-master", "Layer 2 master using Peer to Peer", { + {"ptpengine:transport","ethernet"}, + {"ptpengine:delay_mechanism","P2P"}, + {"ptpengine:preset","masteronly"}, +// {"",""}, + {NULL}} + }, + + { "layer2-p2p-slave", "Layer 2 slave running End to End", { + {"ptpengine:transport","ethernet"}, + {"ptpengine:delay_mechanism","E2E"}, + {"ptpengine:preset","slaveonly"}, +// {"",""}, + {NULL}} + }, + + { "valid-utc-properties", "Basic property set to announce valid UTC, UTC offset and leap seconds", { + {"ptpengine:ptp_timescale","PTP"}, + {"ptpengine:time_traceable","y"}, + {"ptpengine:frequency_traceable","y"}, + {"clock:leap_seconds_file", DATADIR"/"PACKAGE_NAME"/leap-seconds.list"}, + /* + * UTC offset value and UTC offset valid flag + * will be announced if leap file is parsed and up to date + */ + {NULL}} + }, + + { "full-logging", "Enable logging for all facilities (statistics, status, log file)", { + {"global:log_status", "y"}, + {"global:status_file", "/var/run/ptpd2.status"}, + {"global:statistics_log_interval", "1"}, + {"global:lock_file", "/var/run/ptpd2.pid"}, + {"global:log_statistics", "y"}, + {"global:statistics_file", "/var/log/ptpd2.statistics"}, +// {"global:statistics_file_truncate", "n"}, + {"global:log_file", "/var/log/ptpd2.log"}, + {"global:statistics_timestamp_format", "both"}, + {NULL}} + }, + + { "full-logging-instance", "Logging for all facilities using 'instance' variable which the user should provide", { + {"global:log_status", "y"}, + {"global:status_file", "@rundir@/ptpd2.@instance@.status"}, + {"global:statistics_log_interval", "1"}, + {"global:lock_file", "@rundir@/ptpd2.@instance@.pid"}, + {"global:log_statistics", "y"}, + {"global:statistics_file", "@logdir@/ptpd2.@instance@.statistics"}, +// {"global:statistics_file_truncate", "n"}, + {"global:log_file", "@logdir@/ptpd2.@instance@.log"}, + {"global:statistics_timestamp_format", "both"}, + {NULL}} + }, + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + { "score-autosar", "SCORE specific AUTOSAR profile", { + {"ptpengine:domain","0"}, + {"ptpengine:preset","slaveonly"}, + {"ptpengine:transport","ethernet"}, + {"ptpengine:delay_mechanism","DELAY_DISABLED"}, + {"ptpengine:disable_bmca", "y"}, + {"score:autosar", "y"}, + {"score:globaltimepropagationdelay", "0.0"}, + {NULL}} + }, + + // ##### SCORE MODIFICATION END ##### +#endif + {NULL} +}; + +/* =============== configuration templates end ===============*/ + +/* template of a template looks like this... */ + +/* + { "template-name", "template description", { + {"section:key","value"}, + {"",""}, + {NULL}} + }, + + { "", "", { + {"",""}, + {"",""}, + {NULL}} + }, + + +*/ + +/* Load all rtOpts defaults */ +void +loadDefaultSettings( RunTimeOpts* rtOpts ) +{ + + /* Wipe the memory first to avoid unconsistent behaviour - no need to set Boolean to FALSE, int to 0 etc. */ + memset(rtOpts, 0, sizeof(RunTimeOpts)); + + rtOpts->logAnnounceInterval = DEFAULT_ANNOUNCE_INTERVAL; +// ##### SCORE MODIFICATION BEGIN ##### + rtOpts->logSyncInterval = log2(((double)(ptpConfig.timebaseConfig.syncPeriodMs)/SCORE_PTP_DEFAULT_SYNC_PERIOD)); +// ##### SCORE MODIFICATION END ##### + rtOpts->logMinPdelayReqInterval = DEFAULT_PDELAYREQ_INTERVAL; + rtOpts->clockQuality.clockAccuracy = DEFAULT_CLOCK_ACCURACY; + rtOpts->clockQuality.clockClass = DEFAULT_CLOCK_CLASS; + rtOpts->clockQuality.offsetScaledLogVariance = DEFAULT_CLOCK_VARIANCE; + rtOpts->priority1 = DEFAULT_PRIORITY1; + rtOpts->priority2 = DEFAULT_PRIORITY2; + rtOpts->domainNumber = DEFAULT_DOMAIN_NUMBER; + rtOpts->portNumber = NUMBER_PORTS; + + rtOpts->anyDomain = FALSE; + + rtOpts->transport = UDP_IPV4; + + /* timePropertiesDS */ + rtOpts->timeProperties.currentUtcOffsetValid = DEFAULT_UTC_VALID; + rtOpts->timeProperties.currentUtcOffset = DEFAULT_UTC_OFFSET; + rtOpts->timeProperties.timeSource = INTERNAL_OSCILLATOR; + rtOpts->timeProperties.timeTraceable = FALSE; + rtOpts->timeProperties.frequencyTraceable = FALSE; + rtOpts->timeProperties.ptpTimescale = TRUE; + + rtOpts->ipMode = IPMODE_MULTICAST; + rtOpts->dot1AS = FALSE; + + rtOpts->disableUdpChecksums = TRUE; + + rtOpts->unicastNegotiation = FALSE; + rtOpts->unicastNegotiationListening = FALSE; + rtOpts->disableBMCA = FALSE; + rtOpts->unicastGrantDuration = 300; + rtOpts->unicastAcceptAny = FALSE; + rtOpts->unicastPortMask = 0; + + rtOpts->noAdjust = NO_ADJUST; // false + rtOpts->logStatistics = TRUE; + rtOpts->statisticsTimestamp = TIMESTAMP_DATETIME; + + rtOpts->periodicUpdates = FALSE; /* periodically log a status update */ + + /* Deep display of all packets seen by the daemon */ + rtOpts->displayPackets = FALSE; + + rtOpts->s = DEFAULT_DELAY_S; + rtOpts->inboundLatency.nanoseconds = DEFAULT_INBOUND_LATENCY; + rtOpts->outboundLatency.nanoseconds = DEFAULT_OUTBOUND_LATENCY; + rtOpts->max_foreign_records = DEFAULT_MAX_FOREIGN_RECORDS; + rtOpts->nonDaemon = FALSE; + + /* + * defaults for new options + */ + rtOpts->ignore_delayreq_interval_master = FALSE; + rtOpts->do_IGMP_refresh = TRUE; + rtOpts->useSysLog = FALSE; + rtOpts->announceReceiptTimeout = DEFAULT_ANNOUNCE_RECEIPT_TIMEOUT; +#ifdef RUNTIME_DEBUG + rtOpts->debug_level = LOG_INFO; /* by default debug messages as disabled, but INFO messages and below are printed */ +#endif + rtOpts->ttl = 64; + rtOpts->delayMechanism = DEFAULT_DELAY_MECHANISM; + rtOpts->noResetClock = DEFAULT_NO_RESET_CLOCK; + rtOpts->portDisabled = FALSE; + rtOpts->stepOnce = FALSE; + rtOpts->stepForce = FALSE; +#ifdef HAVE_LINUX_RTC_H + rtOpts->setRtc = FALSE; +#endif /* HAVE_LINUX_RTC_H */ + + rtOpts->clearCounters = FALSE; + rtOpts->statisticsLogInterval = 0; + + rtOpts->initial_delayreq = DEFAULT_DELAYREQ_INTERVAL; + rtOpts->logMinDelayReqInterval = DEFAULT_DELAYREQ_INTERVAL; + rtOpts->autoDelayReqInterval = TRUE; + rtOpts->masterRefreshInterval = 60; + + /* maximum values for unicast negotiation */ + rtOpts->logMaxPdelayReqInterval = 5; + rtOpts->logMaxDelayReqInterval = 5; + rtOpts->logMaxSyncInterval = 5; + rtOpts->logMaxAnnounceInterval = 5; + + rtOpts->drift_recovery_method = DRIFT_KERNEL; + strncpy(rtOpts->lockDirectory, DEFAULT_LOCKDIR, PATH_MAX); + strncpy(rtOpts->driftFile, DEFAULT_DRIFTFILE, PATH_MAX); +/* strncpy(rtOpts->lockFile, DEFAULT_LOCKFILE, PATH_MAX); */ + rtOpts->autoLockFile = FALSE; + rtOpts->snmpEnabled = FALSE; + rtOpts->snmpTrapsEnabled = FALSE; + rtOpts->alarmsEnabled = FALSE; + rtOpts->alarmInitialDelay = 0; + rtOpts->alarmMinAge = 30; + /* This will only be used if the "none" preset is configured */ +#ifndef PTPD_SLAVE_ONLY + rtOpts->slaveOnly = FALSE; +#else + rtOpts->slaveOnly = TRUE; +#endif /* PTPD_SLAVE_ONLY */ + /* Otherwise default to slave only via the preset */ + rtOpts->selectedPreset = PTP_PRESET_SLAVEONLY; + rtOpts->pidAsClockId = FALSE; + + strncpy(rtOpts->portDescription,"ptpd", sizeof(rtOpts->portDescription)); + + /* highest possible */ + rtOpts->logLevel = LOG_ALL; + + /* ADJ_FREQ_MAX by default */ + rtOpts->servoMaxPpb = ADJ_FREQ_MAX / 1000; + /* kP and kI are scaled to 10000 and are gains now - values same as originally */ + rtOpts->servoKP = 0.1; + rtOpts->servoKI = 0.001; + + rtOpts->servoDtMethod = DT_CONSTANT; + /* when measuring dT, use a maximum of 5 sync intervals (would correspond to avg 20% discard rate) */ + rtOpts->servoMaxdT = 5.0; + + /* disabled by default */ + rtOpts->announceTimeoutGracePeriod = 0; + + /* currentUtcOffsetValid compatibility flags */ + rtOpts->alwaysRespectUtcOffset = TRUE; + rtOpts->preferUtcValid = FALSE; + rtOpts->requireUtcValid = FALSE; + + /* Try 46 for expedited forwarding */ + rtOpts->dscpValue = 0; + +#if (defined(linux) && defined(HAVE_SCHED_H)) || defined(HAVE_SYS_CPUSET_H) || defined (__QNXNTO__) + rtOpts-> cpuNumber = -1; +#endif /* (linux && HAVE_SCHED_H) || HAVE_SYS_CPUSET_H*/ + +#ifdef PTPD_STATISTICS + + rtOpts->oFilterMSConfig.enabled = FALSE; + rtOpts->oFilterMSConfig.discard = TRUE; + rtOpts->oFilterMSConfig.autoTune = TRUE; + rtOpts->oFilterMSConfig.stepDelay = FALSE; + rtOpts->oFilterMSConfig.alwaysFilter = FALSE; + rtOpts->oFilterMSConfig.stepThreshold = 1000000; + rtOpts->oFilterMSConfig.stepLevel = 500000; + rtOpts->oFilterMSConfig.capacity = 20; + rtOpts->oFilterMSConfig.threshold = 1.0; + rtOpts->oFilterMSConfig.weight = 1; + rtOpts->oFilterMSConfig.minPercent = 20; + rtOpts->oFilterMSConfig.maxPercent = 95; + rtOpts->oFilterMSConfig.thresholdStep = 0.1; + rtOpts->oFilterMSConfig.minThreshold = 0.1; + rtOpts->oFilterMSConfig.maxThreshold = 5.0; + rtOpts->oFilterMSConfig.delayCredit = 200; + rtOpts->oFilterMSConfig.creditIncrement = 10; + rtOpts->oFilterMSConfig.maxDelay = 1500; + + rtOpts->oFilterSMConfig.enabled = FALSE; + rtOpts->oFilterSMConfig.discard = TRUE; + rtOpts->oFilterSMConfig.autoTune = TRUE; + rtOpts->oFilterSMConfig.stepDelay = FALSE; + rtOpts->oFilterSMConfig.alwaysFilter = FALSE; + rtOpts->oFilterSMConfig.stepThreshold = 1000000; + rtOpts->oFilterSMConfig.stepLevel = 500000; + rtOpts->oFilterSMConfig.capacity = 20; + rtOpts->oFilterSMConfig.threshold = 1.0; + rtOpts->oFilterSMConfig.weight = 1; + rtOpts->oFilterSMConfig.minPercent = 20; + rtOpts->oFilterSMConfig.maxPercent = 95; + rtOpts->oFilterSMConfig.thresholdStep = 0.1; + rtOpts->oFilterSMConfig.minThreshold = 0.1; + rtOpts->oFilterSMConfig.maxThreshold = 5.0; + rtOpts->oFilterSMConfig.delayCredit = 200; + rtOpts->oFilterSMConfig.creditIncrement = 10; + rtOpts->oFilterSMConfig.maxDelay = 1500; + + rtOpts->filterMSOpts.enabled = FALSE; + rtOpts->filterMSOpts.filterType = FILTER_MIN; + rtOpts->filterMSOpts.windowSize = 4; + rtOpts->filterMSOpts.windowType = WINDOW_SLIDING; + + rtOpts->filterSMOpts.enabled = FALSE; + rtOpts->filterSMOpts.filterType = FILTER_MIN; + rtOpts->filterSMOpts.windowSize = 4; + rtOpts->filterSMOpts.windowType = WINDOW_SLIDING; + + /* How often refresh statistics (seconds) */ + rtOpts->statsUpdateInterval = 30; + /* Servo stability detection settings follow */ + rtOpts->servoStabilityDetection = FALSE; + /* Stability threshold (ppb) - observed drift std dev value considered stable */ + rtOpts->servoStabilityThreshold = 10; + /* How many consecutive statsUpdateInterval periods of observed drift std dev within threshold means stable servo */ + rtOpts->servoStabilityPeriod = 1; + /* How many minutes without servo stabilisation means servo has not stabilised */ + rtOpts->servoStabilityTimeout = 10; + /* How long to wait for one-way delay prefiltering */ + rtOpts->calibrationDelay = 0; + /* if set to TRUE and maxDelay is defined, only check against threshold if servo is stable */ + rtOpts->maxDelayStableOnly = FALSE; + /* if set to non-zero, reset slave if more than this amount of consecutive delay measurements was above maxDelay */ + rtOpts->maxDelayMaxRejected = 0; +#endif + + /* status file options */ + rtOpts->statusFileUpdateInterval = 1; + + rtOpts->ofmAlarmThreshold = 0; + + /* panic mode options */ + rtOpts->enablePanicMode = FALSE; + rtOpts->panicModeDuration = 2; + rtOpts->panicModeExitThreshold = 0; + + /* full network reset after 5 times in listening */ + rtOpts->maxListen = 5; + + rtOpts->panicModeReleaseClock = FALSE; + rtOpts->ntpOptions.enableEngine = FALSE; + rtOpts->ntpOptions.enableControl = FALSE; + rtOpts->ntpOptions.enableFailover = FALSE; + rtOpts->ntpOptions.failoverTimeout = 120; + rtOpts->ntpOptions.checkInterval = 15; + rtOpts->ntpOptions.keyId = 0; + strncpy(rtOpts->ntpOptions.hostAddress,"localhost",MAXHOSTNAMELEN); /* not configurable, but could be */ + rtOpts->preferNTP = FALSE; + + rtOpts->leapSecondPausePeriod = 5; + /* by default, announce the leap second 12 hours before the event: + * Clause 9.4 paragraph 5 */ + rtOpts->leapSecondNoticePeriod = 43200; + rtOpts->leapSecondHandling = LEAP_ACCEPT; + rtOpts->leapSecondSmearPeriod = 86400; + +/* timing domain */ + rtOpts->idleTimeout = 120; /* idle timeout */ + rtOpts->electionDelay = 15; /* anti-flapping delay */ + +/* Log file settings */ + + rtOpts->statisticsLog.logID = "statistics"; + rtOpts->statisticsLog.openMode = "a+"; + rtOpts->statisticsLog.logFP = NULL; + rtOpts->statisticsLog.truncateOnReopen = FALSE; + rtOpts->statisticsLog.unlinkOnClose = FALSE; + rtOpts->statisticsLog.maxSize = 0; + + rtOpts->recordLog.logID = "record"; + rtOpts->recordLog.openMode = "a+"; + rtOpts->recordLog.logFP = NULL; + rtOpts->recordLog.truncateOnReopen = FALSE; + rtOpts->recordLog.unlinkOnClose = FALSE; + rtOpts->recordLog.maxSize = 0; + + rtOpts->eventLog.logID = "log"; + rtOpts->eventLog.openMode = "a+"; + rtOpts->eventLog.logFP = NULL; + rtOpts->eventLog.truncateOnReopen = FALSE; + rtOpts->eventLog.unlinkOnClose = FALSE; + rtOpts->eventLog.maxSize = 0; + + rtOpts->statusLog.logID = "status"; + rtOpts->statusLog.openMode = "w"; + strncpy(rtOpts->statusLog.logPath, DEFAULT_STATUSFILE, PATH_MAX); + rtOpts->statusLog.logFP = NULL; + rtOpts->statusLog.truncateOnReopen = FALSE; + rtOpts->statusLog.unlinkOnClose = TRUE; + +/* Management message support settings */ + rtOpts->managementEnabled = TRUE; + rtOpts->managementSetEnable = FALSE; + +/* IP ACL settings */ + + rtOpts->timingAclEnabled = FALSE; + rtOpts->managementAclEnabled = FALSE; + rtOpts->timingAclOrder = ACL_DENY_PERMIT; + rtOpts->managementAclOrder = ACL_DENY_PERMIT; + + rtOpts->clockUpdateTimeout = 0; + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + + rtOpts->scoreConfig.autosar = FALSE; + + rtOpts->scoreConfig.GlobalTimePropagationDelay = 0.0; +#endif + // ##### SCORE MODIFICATION END ##### +} + +/* The PtpEnginePreset structure for reference: + +typedef struct { + + char* presetName; + Boolean slaveOnly; + Boolean noAdjust; + UInteger8_option clockClass; + +} PtpEnginePreset; +*/ + +PtpEnginePreset +getPtpPreset(int presetNumber, RunTimeOpts* rtOpts) +{ + + PtpEnginePreset ret; + + memset(&ret,0,sizeof(ret)); + + switch(presetNumber) { + + case PTP_PRESET_SLAVEONLY: + ret.presetName="slaveonly"; + ret.slaveOnly = TRUE; + ret.noAdjust = FALSE; + ret.clockClass.minValue = SLAVE_ONLY_CLOCK_CLASS; + ret.clockClass.maxValue = SLAVE_ONLY_CLOCK_CLASS; + ret.clockClass.defaultValue = SLAVE_ONLY_CLOCK_CLASS; + break; + case PTP_PRESET_MASTERSLAVE: + ret.presetName = "masterslave"; + ret.slaveOnly = FALSE; + ret.noAdjust = FALSE; + ret.clockClass.minValue = 128; + ret.clockClass.maxValue = 254; + ret.clockClass.defaultValue = DEFAULT_CLOCK_CLASS; + break; + case PTP_PRESET_MASTERONLY: + ret.presetName = "masteronly"; + ret.slaveOnly = FALSE; + ret.noAdjust = TRUE; + ret.clockClass.minValue = 0; + ret.clockClass.maxValue = 127; + ret.clockClass.defaultValue = DEFAULT_CLOCK_CLASS__APPLICATION_SPECIFIC_TIME_SOURCE; + break; + default: + ret.presetName = "none"; + ret.slaveOnly = rtOpts->slaveOnly; + ret.noAdjust = rtOpts->noAdjust; + ret.clockClass.minValue = 0; + ret.clockClass.maxValue = 255; + ret.clockClass.defaultValue = rtOpts->clockQuality.clockClass; + } + + return ret; +} + +/* Apply template search from dictionary src to dictionary dst */ +static int +applyTemplateFromDictionary(dictionary *dict, dictionary *src, char *search, int overwrite) { + + char *key, *value, *pos; + int i = 0; + int found; + + /* -1 on not found, 0+ on actual applies */ + found = -1; + + for(i=0; in; i++) { + key = src->key[i]; + value = src->val[i]; + pos = strstr(key, ":"); + if(value != NULL && pos != NULL) { + *pos = '\0'; + pos++; + if(!strcmp(search, key) && (strstr(pos,":") != NULL)) { + if(found < 0) found = 0; + if( overwrite || !CONFIG_ISSET(pos)) { + DBG("template %s setting %s to %s\n", search, pos, value); + dictionary_set(dict, pos, value); + found++; + } + } + /* reverse the damage - no need for strdup() */ + pos--; + *pos = ':'; + } + } + + return found; + +} + +static void loadFileList(dictionary *dict, char *list) { + + char* stash; + char* text_; + char* text__; + char *filename; + + if(dict == NULL) return; + if(!strlen(list)) return; + + text_=strdup(list); + + for(text__=text_;; text__=NULL) { + filename=strtok_r(text__,", ;\t",&stash); + if(filename==NULL) break; + if(!iniparser_merge_file(dict, filename,1)) { + ERROR("Could not load template file %s\n", filename); + } else { + INFO("Loaded template file %s\n",filename); + } + } + + if(text_ != NULL) { + free(text_); + } + +} + +/* split list to tokens, look for each template and apply it if found */ +int +applyConfigTemplates(dictionary *target, char *templateNames, char *files) { + + ConfigTemplate *template = NULL; + TemplateOption *option = NULL; + char *templateName = NULL; + int iFound = -1; + int fFound = -1; + + dictionary *fileDict; + dictionary *dict; + + char* stash; + char* text_; + char* text__; + + if(target == NULL) { + return 0; + } + + if(!strlen(templateNames)) return 0; + + fileDict = dictionary_new(0); + dict = dictionary_new(0); + + loadFileList(fileDict, DEFAULT_TEMPLATE_FILE); + loadFileList(fileDict, files); + + text_=strdup(templateNames); + + for(text__=text_;; text__=NULL) { + + iFound = -1; + fFound = -1; + + /* apply from built-in templates */ + templateName=strtok_r(text__,", ;\t",&stash); + if(templateName==NULL) break; + + template = (ConfigTemplate*)configTemplates; + while(template->name != NULL) { + if(!strcmp(template->name, templateName)) { + if(iFound < 0) iFound = 0; + DBG("Loading built-in config template %s\n", template->name); + option = (TemplateOption*)template->options; + while(option->name != NULL) { + dictionary_set(dict, option->name, option->value); + DBG("----> %s = %s",option->name, option->value); + option++; + iFound++; + } + break; + } + template++; + } + + /* apply from previously loaded files */ + fFound = applyTemplateFromDictionary(dict, fileDict, templateName, 1); + + if (!IS_QUIET()) { + if((iFound < 0) && (fFound < 0)) { + WARNING("Configuration template \"%s\" not found\n", templateName); + } else { + if(iFound < 0) iFound = 0; + if(fFound < 0) fFound = 0; + NOTICE("Applied configuration template \"%s\" (%d matches)\n", templateName, + iFound + fFound); + } + } + + } + + if(text_ != NULL) { + free(text_); + } + + /* preserve existing settings */ + dictionary_merge(dict, target, 0 , !IS_QUIET(), NULL); + dictionary_del(&fileDict); + dictionary_del(&dict); + return 0; + +} + +/* dump the list of templates provided */ +void +dumpConfigTemplates() { + + ConfigTemplate *template = NULL; + TemplateOption *option = NULL; + printf("\n"); + template = (ConfigTemplate*)configTemplates; + while(template->name != NULL) { + printf(" Template: %s\n", template->name); + printf("Description: %s\n\n", template->description); + option = (TemplateOption*)template->options; + while(option->name != NULL) { + printf("\t\t%s=\"%s\"\n", option->name, option->value); + option++; + } + template++; + printf("\n-\n\n"); + } + +} diff --git a/src/ptpd/src/dep/configdefaults.h b/src/ptpd/src/dep/configdefaults.h new file mode 100644 index 0000000..4bca18a --- /dev/null +++ b/src/ptpd/src/dep/configdefaults.h @@ -0,0 +1,52 @@ +/** + * @file configdefaults.h + * + * @brief definitions related to config templates and defaults + * + * + */ + +#ifndef PTPD_CONFIGDEFAULTS_H_ +#define PTPD_CONFIGDEFAULTS_H_ + +#include +#include "iniparser/iniparser.h" + +#define DEFAULT_TEMPLATE_FILE DATADIR"/"PACKAGE_NAME"/templates.conf" + +typedef struct { + char * name; + char * value; +} TemplateOption; + +typedef struct { + char * name; + char * description; + TemplateOption options[100]; +} ConfigTemplate; + +/* Structure defining a PTP engine preset */ +typedef struct { + + char* presetName; + Boolean slaveOnly; + Boolean noAdjust; + UInteger8_option clockClass; + +} PtpEnginePreset; + +/* Preset definitions */ +enum { + PTP_PRESET_NONE, + PTP_PRESET_SLAVEONLY, + PTP_PRESET_MASTERSLAVE, + PTP_PRESET_MASTERONLY, + PTP_PRESET_MAX +}; + +void loadDefaultSettings( RunTimeOpts* rtOpts ); +int applyConfigTemplates(dictionary *dict, char *templateNames, char *files); +PtpEnginePreset getPtpPreset(int presetNumber, RunTimeOpts* rtOpts); +void dumpConfigTemplates(); + +#endif /* PTPD_CONFIGDEFAULTS_H_ */ diff --git a/src/ptpd/src/dep/constants_dep.h b/src/ptpd/src/dep/constants_dep.h new file mode 100644 index 0000000..f8f69e5 --- /dev/null +++ b/src/ptpd/src/dep/constants_dep.h @@ -0,0 +1,260 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +/* constants_dep.h */ + +#ifndef CONSTANTS_DEP_H +#define CONSTANTS_DEP_H + +/** +*\file +* \brief Plateform-dependent constants definition +* +* This header defines all includes and constants which are plateform-dependent +* +* ptpdv2 is only implemented for linux, NetBSD and FreeBSD + */ + +/* platform dependent */ + +#if !defined(linux) && !defined(__NetBSD__) && !defined(__FreeBSD__) && \ + !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__sun) && !defined(__QNXNTO__) +#error PTPD hasn't been ported to this OS - should be possible \ +if it's POSIX compatible, if you succeed, report it to ptpd-devel@sourceforge.net +#endif + +#ifdef linux +#include +#include +#include +#include +#define IFACE_NAME_LENGTH IF_NAMESIZE +#define NET_ADDRESS_LENGTH INET_ADDRSTRLEN + +#define IFCONF_LENGTH 10 + +#define octet ether_addr_octet +#endif /* linux */ + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__sun) || defined(__QNXNTO__) +# include +# include +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif /* HAVE_SYS_SOCKIO_H */ +# include +# include +# include +# include +#ifdef HAVE_NET_IF_ETHER_H +# include +#endif +#ifdef HAVE_SYS_UIO_H +# include +#endif +#ifdef HAVE_NET_ETHERNET_H +# include +#endif +#include +# define IFACE_NAME_LENGTH IF_NAMESIZE +# define NET_ADDRESS_LENGTH INET_ADDRSTRLEN + +#ifdef HAVE_SYS_PARAM_H +#include +#endif /* HAVE_SYS_PARAM_H */ + +#ifdef __QNXNTO__ +#include +#include +#define BSD_INTERFACE_FUNCTIONS +#endif /* __QNXNTO __ */ + + +#if !defined(ETHER_ADDR_LEN) && defined(ETHERADDRL) +# define ETHER_ADDR_LEN ETHERADDRL +#endif /* ETHER_ADDR_LEN && ETHERADDRL */ + +#ifndef ETHER_HDR_LEN +# define ETHER_HDR_LEN sizeof (struct ether_header) +#endif /* ETHER_ADDR_LEN && ETHERADDRL */ + + +# define IFCONF_LENGTH 10 + +# define adjtimex ntp_adjtime + + +#endif + +#ifdef HAVE_MACHINE_ENDIAN_H +# include +#endif /* HAVE_MACHINE_ENDIAN_H */ + +#ifdef HAVE_ENDIAN_H +# include +#endif /* HAVE_ENDIAN_H */ + +#ifdef HAVE_SYS_ISA_DEFS_H +# include +#endif /* HAVE_SYS_ISA_DEFS_H */ + +# if BYTE_ORDER == LITTLE_ENDIAN || defined(_LITTLE_ENDIAN) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) +# define PTPD_LSBF +# elif BYTE_ORDER == BIG_ENDIAN || defined(_BIG_ENDIAN) || (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) +# define PTPD_MSBF +# endif + +#define CLOCK_IDENTITY_LENGTH 8 +#define ADJ_FREQ_MAX 500000 + +/* UDP/IPv4 dependent */ +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001UL +#endif + +#define SUBDOMAIN_ADDRESS_LENGTH 4 +#define PORT_ADDRESS_LENGTH 2 +#define PTP_UUID_LENGTH 6 +#define CLOCK_IDENTITY_LENGTH 8 +#define FLAG_FIELD_LENGTH 2 + +#define PACKET_SIZE 300 +#define PACKET_BEGIN_UDP (ETHER_HDR_LEN + sizeof(struct ip) + \ + sizeof(struct udphdr)) +#define PACKET_BEGIN_ETHER (ETHER_HDR_LEN) + +#define PTP_EVENT_PORT 319 +#define PTP_GENERAL_PORT 320 + +#define DEFAULT_PTP_DOMAIN_ADDRESS "224.0.1.129" +#define PEER_PTP_DOMAIN_ADDRESS "224.0.0.107" + +/* 802.3 Support */ + +#define PTP_ETHER_DST "01:1b:19:00:00:00" +#define PTP_ETHER_TYPE 0x88f7 +#define PTP_ETHER_PEER "01:80:c2:00:00:0E" + +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS +#define PTP_ETHER_8021AS "01:80:c2:00:00:0E" +#endif +// ##### SCORE MODIFICATION END ##### + +#ifdef PTPD_UNICAST_MAX +#define UNICAST_MAX_DESTINATIONS PTPD_UNICAST_MAX +#else +#define UNICAST_MAX_DESTINATIONS 16 +#endif /* PTPD_UNICAST_MAX */ + +/* dummy clock driver designation in preparation for generic clock driver API */ +#define DEFAULT_CLOCKDRIVER "kernelclock" +/* default lock file location and mode */ +#define DEFAULT_LOCKMODE F_WRLCK +#define DEFAULT_LOCKDIR "/var/run" +#define DEFAULT_LOCKFILE_NAME PTPD_PROGNAME".lock" +//define DEFAULT_LOCKFILE_PATH DEFAULT_LOCKDIR"/"DEFAULT_LOCKFILE_NAME +#define DEFAULT_FILE_PERMS (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) + +/* default drift file location */ +#define DEFAULT_DRIFTFILE "/etc/"PTPD_PROGNAME"_"DEFAULT_CLOCKDRIVER".drift" + +/* default status file location */ +#define DEFAULT_STATUSFILE DEFAULT_LOCKDIR"/"PTPD_PROGNAME".status" + +/* Highest log level (default) catches all */ +#define LOG_ALL LOG_DEBUGV + +/* Difference between Unix time / UTC and NTP time */ +#define NTP_EPOCH 2208988800ULL + +/* wait a maximum of 10 ms for a late TX timestamp */ +#define LATE_TXTIMESTAMP_US 10000 + +/* drift recovery metod for use with -F */ +enum { + DRIFT_RESET = 0, + DRIFT_KERNEL, + DRIFT_FILE +}; +/* IP transmission mode */ +enum { + IPMODE_MULTICAST = 0, + IPMODE_UNICAST, + IPMODE_HYBRID, +#if 0 + IPMODE_UNICAST_SIGNALING +#endif +}; + +/* log timestamp mode */ +enum { + TIMESTAMP_DATETIME, + TIMESTAMP_UNIX, + TIMESTAMP_BOTH +}; + +/* servo dT calculation mode */ +enum { + DT_NONE, + DT_CONSTANT, + DT_MEASURED +}; + +/* StatFilter op type */ +enum { + FILTER_NONE, + FILTER_MEAN, + FILTER_MIN, + FILTER_MAX, + FILTER_ABSMIN, + FILTER_ABSMAX, + FILTER_MEDIAN, + FILTER_MAXVALUE +}; + +/* StatFilter window type */ +enum { + WINDOW_INTERVAL, + WINDOW_SLIDING, +// WINDOW_OVERLAPPING +}; + +/* Leap second handling */ +enum { + LEAP_ACCEPT, + LEAP_IGNORE, + LEAP_STEP, + LEAP_SMEAR +}; + +/* Alarm codes */ +enum { + ALARM_PORTSTATE, + ALARM_OFFSET_THRESHOLD, + ALARM_CLOCK_STEP, + ALARM_OFFSET_1SEC +}; + +#define MM_STARTING_BOUNDARY_HOPS 0x7fff + +/* others */ + +/* bigger screen size constants */ +#define SCREEN_BUFSZ 228 +#define SCREEN_MAXSZ 180 + +/* default size for string buffers */ +#define BUF_SIZE 1000 + +#define NANOSECONDS_MAX 999999999 + +// limit operator messages to once every X seconds +#define OPERATOR_MESSAGES_INTERVAL 300.0 + +#define MAX_SEQ_ERRORS 50 + +#define MAXTIMESTR 32 + +#endif /*CONSTANTS_DEP_H_*/ diff --git a/src/ptpd/src/dep/daemonconfig.c b/src/ptpd/src/dep/daemonconfig.c new file mode 100644 index 0000000..a6bd4bb --- /dev/null +++ b/src/ptpd/src/dep/daemonconfig.c @@ -0,0 +1,3220 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ +/*- + * Copyright (c) 2013-2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file daemonconfig.c + * @date Sun May 27 00:45:32 2013 + * + * @brief Code to handle configuration file and default settings + * + * Functions in this file deal with config file parsing, reloading, + * loading default parameters, parsing command-line options, printing + * help output, etc. + * + */ + +#include "../ptpd.h" + +/*- + * Helper macros - this is effectively the API for using the new config file interface. + * The macros below cover all operations required in parsing the configuration. + * This allows the parseConfig() function to be very straightforward and easy to expand + * - it pretty much only uses macros at this stage. The use of macros reduces the + * complexity of the parseConfig fuction. A lot of the macros are designed to work + * within parseConfig - they assume the existence of the "dictionary*" dict variable. + */ + +static void printComment(const char* helptext); + +static int configSettingChanged(dictionary *oldConfig, dictionary *newConfig, const char *key); + +static void warnRestart(const char *key, int flags); + +static int configMapBoolean(int opCode, void *opArg, dictionary* dict, + dictionary *target, const char * key, int restartFlags, Boolean *var, Boolean def, const char* helptext); + +static int configMapString(int opCode, void *opArg, dictionary *dict, + dictionary *target, const char *key, int restartFlags, char *var, int size, char *def, const char* helptext); + +static int checkRangeInt(dictionary *dict, const char *key, int rangeFlags, int minBound, int maxBound); + +static int configMapInt(int opCode, void *opArg, dictionary *dict, + dictionary *target, const char *key, int restartFlags, int intType, void *var, int def, + const char *helptext, int rangeFlags, int minBound, int maxBound); + +static int checkRangeDouble(dictionary *dict, const char *key, int rangeFlags, double minBound, double maxBound); + +static int configMapDouble(int opCode, void *opArg, dictionary *dict, + dictionary *target, const char *key, int restartFlags, double *var, double def, + const char *helptext, int rangeFlags, double minBound, double maxBound); + +static int configMapSelectValue(int opCode, void *opArg, dictionary *dict, + dictionary *target, const char* key, int restartFlags, uint8_t *var, int def, const char *helptext, ...); + +static void parseUserVariables(dictionary *dict, dictionary *target); + +static void findUnknownSettings(int opCode, dictionary* source, dictionary* dict); + + +/* Basic helper macros */ + +#define STRING_EMPTY(string)\ + (!strcmp(string,"")) + +#define CONFIG_ISSET(key) \ + (strcmp(iniparser_getstring(dict, key, ""),"") != 0) + +#define CONFIG_ISPRESENT(key) \ + (iniparser_find_entry(dict, key) != 0) + +#define CONFIG_ISTRUE(key) \ + (iniparser_getboolean(dict,key,FALSE)==TRUE) + +#define DICT_ISTRUE(dict,key) \ + (iniparser_getboolean(dict,key,FALSE)==TRUE) + +/* Macros handling required settings, triggers, conflicts and dependencies */ + +#define CONFIG_KEY_REQUIRED(key) \ + if( !(opCode & CFGOP_PARSE_QUIET) && !(opCode & CFGOP_HELP_FULL) && !(opCode & CFGOP_HELP_SINGLE) && !CONFIG_ISSET(key) )\ + { \ + ERROR("Configuration error: option \"%s\" is required\n", key); \ + parseResult = FALSE;\ + } + +#define CONFIG_KEY_DEPENDENCY(key1, key2) \ + if( CONFIG_ISSET(key1) && \ + !CONFIG_ISSET(key2)) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("Configuration error: option \"%s\" requires option \"%s\"\n", key1, key2); \ + parseResult = FALSE;\ + } + +#define CONFIG_KEY_CONFLICT(key1, key2) \ + if( CONFIG_ISSET(key1) && \ + CONFIG_ISSET(key2)) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("Configuration error: option \"%s\" cannot be used with option \"%s\"\n", key1, key2); \ + parseResult = FALSE;\ + } + +#define CONFIG_KEY_CONDITIONAL_WARNING_NOTSET(condition,dep,messageText) \ + if ( (condition) && \ + !CONFIG_ISSET(dep) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + WARNING("Warning: %s\n", messageText); \ + } + +#define CONFIG_KEY_CONDITIONAL_WARNING_ISSET(condition,dep,messageText) \ + if ( (condition) && \ + CONFIG_ISSET(dep) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + WARNING("Warning: %s\n", messageText); \ + } + +#define CONFIG_KEY_CONDITIONAL_DEPENDENCY(key,condition,stringval,dep) \ + if ( (condition) && \ + !CONFIG_ISSET(dep) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("Configuration error: option \"%s=%s\" requires option \"%s\"\n", key, stringval, dep); \ + parseResult = FALSE;\ + } + +#define CONFIG_KEY_CONDITIONAL_CONFLICT(key,condition,stringval,dep) \ + if ( (condition) && \ + CONFIG_ISSET(dep) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("Configuration error: option \"%s=%s\" cannot be used with option \"%s\"\n", key, stringval, dep); \ + parseResult = FALSE;\ + } + +#define CONFIG_KEY_VALUE_FORBIDDEN(key,condition,stringval,message) \ + if ( (condition) && \ + CONFIG_ISSET(key) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("Configuration error: option \"%s=%s\" cannot be used: \n%s", key, stringval, message); \ + parseResult = FALSE;\ + } + + +#define CONFIG_KEY_TRIGGER(key,variable,value, otherwise) \ + if (CONFIG_ISSET(key) ) \ + { \ + variable = value;\ + } else { \ + variable = otherwise;\ + } + +#define CONFIG_KEY_CONDITIONAL_TRIGGER(condition,variable,value,otherwise) \ + if ((condition)) \ + { \ + variable = value; \ + } else { \ + variable = otherwise; \ + } + +#define CONFIG_KEY_CONDITIONAL_ASSERTION(key,condition,warningtext) \ + if ( (condition) && \ + CONFIG_ISSET(key) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("%s\n", warningtext); \ + parseResult = FALSE;\ + } + +#define CONFIG_CONDITIONAL_ASSERTION(condition,warningtext) \ + if ( (condition) ) \ + { \ + if(!(opCode & CFGOP_PARSE_QUIET))\ + ERROR("%s\n", warningtext); \ + parseResult = FALSE;\ + } + +/* Macro printing a warning for a deprecated command-line option */ +#define WARN_DEPRECATED_COMMENT( old,new,long,key,comment )\ +printf("Note: The use of '-%c' option is deprecated %s- consider using '-%c' (--%s) or the %s setting\n",\ + old, comment, new, long, key); + +#define WARN_DEPRECATED(old,new,long,key)\ + WARN_DEPRECATED_COMMENT( old,new,long,key,"") + +#define CONFIG_KEY_ALIAS(src,dest) \ + { \ + if(!STRING_EMPTY(dictionary_get(dict,src,""))) {\ + dictionary_set(dict,dest,dictionary_get(dict,src,""));\ + dictionary_unset(dict,src);\ + }\ + } + +/* Output a potentially multi-line string, prefixed with ;s */ +static void +printComment(const char* helptext) +{ + + int i, len; + len = strlen(helptext); + + if(len > 0) + printf("\n; "); + + for (i = 0; i < len; i++) { + printf("%c",helptext[i]); + if(helptext[i]=='\n') { + printf("; "); + while(i < len) { + if( helptext[++i]!=' ' && helptext[i]!='\t') { + i--; + break; + } + } + } + } + printf("\n"); + +} + +static int +configSettingChanged(dictionary *oldConfig, dictionary *newConfig, const char *key) +{ + + return(strcmp( + dictionary_get(newConfig, key,""), + dictionary_get(oldConfig, key,"") + ) != 0 ); + +} + +/* warn about restart required if needed */ +static void +warnRestart(const char *key, int flags) +{ + if(flags & PTPD_RESTART_DAEMON) { + NOTIFY("Change of %s setting requires "PTPD_PROGNAME" restart\n",key); + } else { + DBG("Setting %s changed, restart of subsystem %d required\n",key,flags); + } +} + +static int +configMapBoolean(int opCode, void *opArg, dictionary* dict, dictionary *target, + const char * key, int restartFlags, Boolean *var, Boolean def, const char* helptext) +{ + + if(opCode & CFGOP_RESTART_FLAGS) { + if(CONFIG_ISSET(key)) { + *(int*)opArg |= restartFlags; + if(opCode & CFGOP_RESTART_FLAGS && !(opCode & CFGOP_PARSE_QUIET)) { + warnRestart(key, restartFlags); + } + } + return 1; + } else if(opCode & CFGOP_HELP_FULL || opCode & CFGOP_HELP_SINGLE) { + + char *helpKey = (char*)opArg; + + if((opCode & CFGOP_HELP_SINGLE)){ + if (strcmp(key, helpKey)) { + return 1; + } else { + /* this way we tell the caller that we have already found the setting we were looking for */ + helpKey[0] = '\0'; + } + } + + printf("setting: %s (--%s)\n", key, key); + printf(" type: BOOLEAN (value must start with t/T/y/Y/1/f/F/n/N/0)\n"); + printf(" usage: %s\n", helptext); + printf("default: %s\n", def ? "Y" : "N"); + printf("\n"); + return 1; + } else { + if (!CONFIG_ISPRESENT(key)) { + *var = def; + dictionary_set(target,key,(*var)?"Y":"N"); + if(strcmp(helptext, "") && opCode & CFGOP_PRINT_DEFAULT) { + printComment(helptext); + printf("%s = %s\n", key,(*var)?"Y":"N"); + } + return 1; + } else if(!CONFIG_ISSET(key) || iniparser_getboolean(dict,key,-1) == -1) { + if(!(opCode & CFGOP_PARSE_QUIET)) { + ERROR("Configuration error: option \"%s='%s'\" has unknown boolean value: must start with 0/1/t/T/f/F/y/Y/n/N\n",key,iniparser_getstring(dict,key,"")); + } + dictionary_set(target,key,""); /* suppress the "unknown entry" warning for malformed boolean values */ \ + return 0; + } else { + *var=iniparser_getboolean(dict,key,def); + dictionary_set(target,key,(*var)?"Y":"N"); + if(strcmp(helptext, "") && opCode & CFGOP_PRINT_DEFAULT) { + printComment(helptext);\ + printf("%s = %s\n", key,(*var)?"Y":"N"); + } + return 1; + } + } +} + +static int +configMapString(int opCode, void *opArg, dictionary *dict, dictionary *target, + const char *key, int restartFlags, char *var, int size, char *def, const char* helptext) +{ + if(opCode & CFGOP_RESTART_FLAGS) { + if(CONFIG_ISSET(key)) { + *(int*)opArg |= restartFlags; + if(opCode & CFGOP_RESTART_FLAGS && !(opCode & CFGOP_PARSE_QUIET)) { + warnRestart(key, restartFlags); + } + } + return 1; + } else if(opCode & CFGOP_HELP_FULL || opCode & CFGOP_HELP_SINGLE) { + + char *helpKey = (char*)opArg; + + if((opCode & CFGOP_HELP_SINGLE)){ + if (strcmp(key, helpKey)) { + return 1; + } else { + /* this way we tell the caller that we have already found the setting we were looking for */ + helpKey[0] = '\0'; + } + } + + printf("setting: %s (--%s)\n", key, key); + printf(" type: STRING\n"); + printf(" usage: %s\n", helptext); + printf("default: %s\n", (strcmp(def, "") == 0) ? "[none]" : def); + printf("\n");\ + return 1; + } else { + char *tmpstring = iniparser_getstring(dict,key,def); + /* do not overwrite the same pointer with the same pointer */ + if (var!=tmpstring) { + strncpy(var, tmpstring, size); + var[size-1] = '\0'; + } + dictionary_set(target, key, tmpstring); + if(strcmp(helptext,"") && opCode & CFGOP_PRINT_DEFAULT) { + printComment(helptext); + printf("%s = %s\n", key,tmpstring); + } + return 1; + } +} + +static int +checkRangeInt(dictionary *dict, const char *key, int rangeFlags, int minBound, int maxBound) +{ + int tmpdouble = iniparser_getint(dict,key,minBound); + + int ret = 1; + + switch(rangeFlags) { + case RANGECHECK_NONE: + return ret; + case RANGECHECK_RANGE: + ret = !(tmpdouble < minBound || tmpdouble > maxBound); + break; + case RANGECHECK_MIN: + ret = !(tmpdouble < minBound); + break; + case RANGECHECK_MAX: + ret = !(tmpdouble > maxBound); + break; + default: + return 0; + } + + return ret; +} + + +static int +configMapInt(int opCode, void *opArg, dictionary *dict, dictionary *target, const char *key, int restartFlags, int intType, + void *var, int def, const char *helptext, int rangeFlags, + int minBound, int maxBound) +{ + int ret = 0; + + if(opCode & CFGOP_RESTART_FLAGS) { + if(CONFIG_ISSET(key)) { + *(int*)opArg |= restartFlags; + if(opCode & CFGOP_RESTART_FLAGS && !(opCode & CFGOP_PARSE_QUIET)) { + warnRestart(key, restartFlags); + } + } + return 1; + } else if(opCode & CFGOP_HELP_FULL || opCode & CFGOP_HELP_SINGLE) { + + char *helpKey = (char*)opArg; + + if((opCode & CFGOP_HELP_SINGLE)){ + if (strcmp(key, helpKey)) { + return 1; + } else { + /* this way we tell the caller that we have already found the setting we were looking for */ + helpKey[0] = '\0'; + } + } + + switch(rangeFlags) { + case RANGECHECK_NONE: + printf("setting: %s (--%s)\n", key, key); + printf(" type: INT\n");\ + printf(" usage: %s\n", helptext); + printf("default: %d\n", def); + printf("\n"); + return 1; + case RANGECHECK_RANGE: + printf("setting: %s (--%s)\n", key, key); + printf(" type: INT "); + if( minBound != maxBound ) + printf("(min: %d, max: %d)\n", minBound, maxBound); + else + printf("(only value of %d allowed)", minBound); + printf("\n"); + printf(" usage: %s\n", helptext); + printf("default: %d\n", def); + printf("\n"); + return 1; + case RANGECHECK_MIN: + printf("setting: %s (--%s)\n", key, key); + printf(" type: INT (min: %d)\n", minBound); + printf(" usage: %s\n", helptext); + printf("default: %d\n", def); + printf("\n"); + return 1; + case RANGECHECK_MAX: + printf("setting: %s (--%s)\n", key, key); + printf(" type: FLOAT (max: %d)\n", maxBound); + printf(" usage: %s\n", helptext); + printf("default: %d\n", def); + printf("\n"); + return 1; + default: + return 0; + } + } else { + char buf[50]; + int userVar = iniparser_getint(dict,key,def); + + switch(intType) { + case INTTYPE_INT: + *(int*)var = userVar; + break; + case INTTYPE_I8: + *(int8_t*)var = (int8_t)userVar; + break; + case INTTYPE_U8: + *(uint8_t*)var = (uint8_t)userVar; + break; + case INTTYPE_I16: + *(int16_t*)var = (int16_t)userVar; + break; + case INTTYPE_U16: + *(uint16_t*)var = (uint16_t)userVar; + break; + case INTTYPE_I32: + *(int32_t*)var = (int32_t)userVar; + break; + case INTTYPE_U32: + *(uint32_t*)var = (uint32_t)userVar; + break; + default: + break; + } + + memset(buf, 0, 50); + snprintf(buf, 50, "%d", userVar); + dictionary_set(target,key,buf); + if(strcmp(helptext, "") && opCode & CFGOP_PRINT_DEFAULT) { + printComment(helptext); + printf("%s = %s\n", key,buf); + } + ret = checkRangeInt(dict, key, rangeFlags, minBound, maxBound); + + if(!ret && !(opCode & CFGOP_PARSE_QUIET)) { + switch(rangeFlags) { + case RANGECHECK_RANGE: + ERROR("Configuration error: option \"%s=%s\" not within allowed range: %d..%d\n", key, buf, minBound, maxBound); + break; + case RANGECHECK_MIN: + ERROR("Configuration error: option \"%s=%s\" below allowed minimum: %d\n", key, buf, minBound); + break; + case RANGECHECK_MAX: + ERROR("Configuration error: option \"%s=%s\" above allowed maximum: %d\n", key, buf, maxBound); + break; + default: + return ret; + } + } + + return ret; + } +} + +static int +checkRangeDouble(dictionary *dict, const char *key, int rangeFlags, double minBound, double maxBound) +{ + double tmpdouble = iniparser_getdouble(dict,key,minBound); + + int ret = 1; + + switch(rangeFlags) { + case RANGECHECK_NONE: + return ret; + case RANGECHECK_RANGE: + ret = !(tmpdouble < minBound || tmpdouble > maxBound); + break; + case RANGECHECK_MIN: + ret = !(tmpdouble < minBound); + break; + case RANGECHECK_MAX: + ret = !(tmpdouble > maxBound); + break; + default: + return 0; + } + + return ret; +} + +static int +configMapDouble(int opCode, void *opArg, dictionary *dict, dictionary *target, const char *key, int restartFlags, + double *var, double def, const char *helptext, int rangeFlags, + double minBound, double maxBound) +{ + + int ret = 0; + if(opCode & CFGOP_RESTART_FLAGS) { + if(CONFIG_ISSET(key)) { + *(int*)opArg |= restartFlags; + if(opCode & CFGOP_RESTART_FLAGS && !(opCode & CFGOP_PARSE_QUIET)) { + warnRestart(key, restartFlags); + } + } + return 1; + } else if(opCode & CFGOP_HELP_FULL || opCode & CFGOP_HELP_SINGLE) { + + char *helpKey = (char*)opArg; + + if((opCode & CFGOP_HELP_SINGLE)){ + if (strcmp(key, helpKey)) { + return 1; + } else { + /* this way we tell the caller that we have already found the setting we were looking for */ + helpKey[0] = '\0'; + } + } + + switch(rangeFlags) { + case RANGECHECK_NONE: + printf("setting: %s (--%s)\n", key, key); + printf(" type: FLOAT\n");\ + printf(" usage: %s\n", helptext); + printf("default: %f\n", def); + printf("\n"); + return 1; + case RANGECHECK_RANGE: + printf("setting: %s (--%s)\n", key, key); + printf(" type: FLOAT "); + if( minBound != maxBound ) + printf("(min: %f, max: %f)\n", minBound, maxBound); + else + printf("(only value of %f allowed)", minBound); + printf("\n"); + printf(" usage: %s\n", helptext); + printf("default: %f\n", def); + printf("\n"); + return 1; + case RANGECHECK_MIN: + printf("setting: %s (--%s)\n", key, key); + printf(" type: FLOAT(min: %f)\n", minBound); + printf(" usage: %s\n", helptext); + printf("default: %f\n", def); + printf("\n"); + return 1; + case RANGECHECK_MAX: + printf("setting: %s (--%s)\n", key, key); + printf(" type: FLOAT(max: %f)\n", maxBound); + printf(" usage: %s\n", helptext); + printf("default: %f\n", def); + printf("\n"); + return 1; + default: + return 0; + } + } else { + char buf[50]; + *var = iniparser_getdouble(dict,key,def); + memset(buf, 0, 50); + snprintf(buf, 50, "%f", *var); + dictionary_set(target,key,buf); + if(strcmp(helptext, "") && opCode & CFGOP_PRINT_DEFAULT) { + printComment(helptext); + printf("%s = %s\n", key,buf); + } + + ret = checkRangeDouble(dict, key, rangeFlags, minBound, maxBound); + + if(!ret && !(opCode & CFGOP_PARSE_QUIET)) { + switch(rangeFlags) { + case RANGECHECK_RANGE: + ERROR("Configuration error: option \"%s=%f\" not within allowed range: %f..%f\n", key, *var, minBound, maxBound); + break; + case RANGECHECK_MIN: + ERROR("Configuration error: option \"%s=%f\" below allowed minimum: %f\n", key, *var, minBound); + break; + case RANGECHECK_MAX: + ERROR("Configuration error: option \"%s=%f\" above allowed maximum: %f\n", key, *var, maxBound); + break; + default: + return ret; + } + } + + return ret; + } +} + +static int +configMapSelectValue(int opCode, void *opArg, dictionary *dict, dictionary *target, +const char* key, int restartFlags, uint8_t *var, int def, const char *helptext, ...) +{ + + int ret; + int i; + /* fixed maximum to avoid while(1) */ + int maxCount = 100; + char *name = NULL; + char *keyValue; + char *defValue = NULL; + int value; + va_list ap; + int selectedValue = -1; + + char sbuf[SCREEN_BUFSZ]; + int len = 0; + + memset(sbuf, 0, sizeof(sbuf)); + + keyValue = iniparser_getstring(dict, key, ""); + + va_start(ap, helptext); + + for(i=0; i < maxCount; i++) { + + name = (char*)va_arg(ap, char*); + if(name == NULL) break; + + value = (int)va_arg(ap, int); + + len += snprintf(sbuf + len, sizeof(sbuf) - len, "%s ", name); + + if(value == def) { + defValue = strdup(name); + } + + if(!strcmp(name, keyValue)) { + selectedValue = value; + } + + } + + if(!strcmp(keyValue, "")) { + selectedValue = def; + keyValue = iniparser_getstring(dict, key, defValue); + } + + va_end(ap); + if(opCode & CFGOP_RESTART_FLAGS) { + if(CONFIG_ISSET(key)) { + *(int*)opArg |= restartFlags; + if(opCode & CFGOP_RESTART_FLAGS && !(opCode & CFGOP_PARSE_QUIET)) { + warnRestart(key, restartFlags); + } + } + return 1; + } else if(opCode & CFGOP_HELP_FULL || opCode & CFGOP_HELP_SINGLE) { + + char *helpKey = (char*)opArg; + + if((opCode & CFGOP_HELP_SINGLE)){ + if (strcmp(key, helpKey)) { + return 1; + } else { + /* this way we tell the caller that we have already found the setting we were looking for */ + helpKey[0] = '\0'; + } + } + + printf("setting: %s (--%s)\n", key, key); + printf(" type: SELECT\n"); + printf(" usage: %s\n", helptext); + printf("options: %s\n", sbuf); + printf("default: %s\n", defValue); + printf("\n"); + ret = 1; + goto result; + } else { + dictionary_set(target, key, keyValue); + if(selectedValue < 0) { + if(!(opCode & CFGOP_PARSE_QUIET)) { + ERROR("Configuration error: option \"%s\" has unknown value: %s - allowed values: %s\n", key, keyValue, sbuf); + } + ret = 0; + } else { + *var = (uint8_t)selectedValue; + if(strcmp(helptext, "") && opCode & CFGOP_PRINT_DEFAULT) { + printComment(helptext); + printf("; Options: %s\n", sbuf); + printf("%s = %s\n", key,keyValue); + } + ret = 1; + } + } +result: + + if(defValue != NULL) { + free(defValue); + } + + return ret; + +} + +void setConfig(dictionary *dict, const char* key, const char *value) +{ + + if(dict == NULL) { + return; + } + + dictionary_set(dict, key, value); + +} + +static void +parseUserVariables(dictionary *dict, dictionary *target) +{ + + int i = 0; + char *search, *replace, *key; + char varname[100]; + + memset(varname, 0, sizeof(varname)); + + if(dict == NULL) return; + + + for(i = 0; i < dict->n; i++) { + key = dict->key[i]; + /* key starts with "variables" */ + if(strstr(key,"variables:") == key) { + /* this cannot fail if we are here */ + search = strstr(key,":"); + search++; + replace = dictionary_get(dict, key, ""); + snprintf(varname, sizeof(varname), "@%s@", search); + DBGV("replacing %s with %s in config\n", varname, replace); + dictionary_replace(dict, varname, replace); + dictionary_set(target, key, replace); + } + + } + +} + +/** + * Iterate through dictionary dict (template) and check for keys in source + * that are not present in the template - display only. + */ +static void +findUnknownSettings(int opCode, dictionary* source, dictionary* dict) +{ + + int i = 0; + + if( source == NULL || dict == NULL) return; + + for(i = 0; i < source->n; i++) { + /* skip if the key is null or is a section */ + if(source->key[i] == NULL || strstr(source->key[i],":") == NULL) + continue; + if ( !iniparser_find_entry(dict, source->key[i]) && !(opCode & CFGOP_PARSE_QUIET) ) + WARNING("Unknown configuration entry: %s - setting will be ignored\n", source->key[i]); + } +} + +/* + * IT HAPPENS HERE + * + * Map all options from @dict dictionary to corresponding @rtopts fields, + * using existing @rtopts fields as defaults. Return a dictionary free + * of unknown options, with explicitly set defaults. + * NOTE: when adding options, also map them in checkSubsystemRestart to + * ensure correct config reload behaviour. + */ +dictionary* +parseConfig ( int opCode, void *opArg, dictionary* dict, RunTimeOpts *rtOpts ) +{ + +/*- + * This function assumes that rtOpts has got all the defaults loaded, + * hence the default values for all options are taken from rtOpts. + * Therefore loadDefaultSettings should normally be used before parseConfig + */ + + + + /*- + * WARNING: for ease of use, a limited number of keys is set + * via getopt in loadCommanLineOptions(). When renaming settings, make sure + * you check it for inconsistencies. If we decide to + * drop all short options in favour of section:key only, + * this warning can be removed. + */ + /** + * Prepare the target dictionary. Every mapped option will be set in + * the target dictionary, including the defaults which are typically + * not present in the original dictionary. As a result, the target + * will contain all the mapped options with explicitly set defaults + * if they were not present in the original dictionary. In the end + * of this function we return this pointer. The resulting dictionary + * is complete and free of any unknown options. In the end, warning + * is issued for unknown options. On any errors, NULL is returned + */ + + dictionary* target = dictionary_new(0); + + Boolean parseResult = TRUE; + + PtpEnginePreset ptpPreset; + + if(!(opCode & CFGOP_PARSE_QUIET)) { + INFO("Checking configuration\n"); + } + + /* + * apply the configuration template(s) as statically defined in configdefaults.c + * and in template files. The list of templates and files is comma, space or tab separated. + * names. Any templates are applied before anything else: so any settings after this can override + */ + if (CONFIG_ISPRESENT("global:config_templates")) { + applyConfigTemplates( + dict, + dictionary_get(dict, "global:config_templates", ""), + dictionary_get(dict, "global:template_files", "") + ); + + /* also set the template names in the target dictionary */ + dictionary_set(target, "global:config_templates", dictionary_get(dict, "global:config_templates", "")); + dictionary_set(target, "global:template_files", dictionary_get(dict, "global:template_files", "")); + } + + /* set automatic variables */ + + /* @pid@ */ + tmpsnprintf(tmpStr, 20, "%d", (uint32_t)getpid()); + dictionary_set(dict, "variables:pid", tmpStr); + + /* @hostname@ */ + char hostName[MAXHOSTNAMELEN+1]; + memset(hostName, 0, MAXHOSTNAMELEN + 1); + gethostname(hostName, MAXHOSTNAMELEN); + dictionary_set(dict, "variables:hostname", hostName); + + parseUserVariables(dict, target); + + dictionary_unset(dict, "variables:pid"); + dictionary_unset(target, "variables:pid"); + + dictionary_unset(dict, "variables:hostname"); + dictionary_unset(target, "variables:hostname"); + + +/* ============= BEGIN CONFIG MAPPINGS, TRIGGERS AND DEPENDENCIES =========== */ + +/* ===== ptpengine section ===== */ + + CONFIG_KEY_REQUIRED("ptpengine:interface"); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:interface", + PTPD_RESTART_NETWORK, rtOpts->primaryIfaceName, sizeof(rtOpts->primaryIfaceName), rtOpts->primaryIfaceName, + "Network interface to use - eth0, igb0 etc. (required)."); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:backup_interface", + PTPD_RESTART_NETWORK, rtOpts->backupIfaceName, sizeof(rtOpts->backupIfaceName), rtOpts->backupIfaceName, + "Backup network interface to use - eth0, igb0 etc. When no GM available, \n" + " slave will keep alternating between primary and secondary until a GM is found.\n"); + + CONFIG_KEY_TRIGGER("ptpengine:backup_interface", rtOpts->backupIfaceEnabled,TRUE,FALSE); + + /* Preset option names have to be mapped to defined presets - no free strings here */ + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:preset", + PTPD_RESTART_PROTOCOL, &rtOpts->selectedPreset, rtOpts->selectedPreset, + "PTP engine preset:\n" + " none = Defaults, no clock class restrictions\n" + " masteronly = Master, passive when not best master (clock class 0..127)\n" + " masterslave = Full IEEE 1588 implementation:\n" + " Master, slave when not best master\n" + " (clock class 128..254)\n" + " slaveonly = Slave only (clock class 255 only)\n", +#ifndef PTPD_SLAVE_ONLY + (getPtpPreset(PTP_PRESET_NONE, rtOpts)).presetName, PTP_PRESET_NONE, + (getPtpPreset(PTP_PRESET_MASTERONLY, rtOpts)).presetName, PTP_PRESET_MASTERONLY, + (getPtpPreset(PTP_PRESET_MASTERSLAVE, rtOpts)).presetName, PTP_PRESET_MASTERSLAVE, +#endif /* PTPD_SLAVE_ONLY */ + (getPtpPreset(PTP_PRESET_SLAVEONLY, rtOpts)).presetName, PTP_PRESET_SLAVEONLY, NULL + ); + + CONFIG_KEY_CONDITIONAL_CONFLICT("ptpengine:preset", + rtOpts->selectedPreset == PTP_PRESET_MASTERSLAVE, + "masterslave", + "ptpengine:unicast_negotiation"); + + ptpPreset = getPtpPreset(rtOpts->selectedPreset, rtOpts); + + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:transport", + PTPD_RESTART_NETWORK, &rtOpts->transport, rtOpts->transport, + "Transport type for PTP packets. Ethernet transport requires libpcap support.", + "ipv4", UDP_IPV4, +#if 0 + "ipv6", UDP_IPV6, +#endif + "ethernet", IEEE_802_3, NULL + ); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:dot1as", PTPD_UPDATE_DATASETS, &rtOpts->dot1AS, rtOpts->dot1AS, + "Enable TransportSpecific field compatibility with 802.1AS / AVB (requires Ethernet transport)"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:disabled", PTPD_RESTART_PROTOCOL, &rtOpts->portDisabled, rtOpts->portDisabled, + "Disable PTP port. Causes the PTP state machine to stay in PTP_DISABLED state indefinitely,\n" + " until it is re-enabled via configuration change or ENABLE_PORT management message."); + + + CONFIG_KEY_CONDITIONAL_WARNING_ISSET((rtOpts->transport != IEEE_802_3) && rtOpts->dot1AS, + "ptpengine:dot1as", + "802.1AS compatibility can only be used with the Ethernet transport\n"); + + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->transport != IEEE_802_3, rtOpts->dot1AS,FALSE, rtOpts->dot1AS); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:ip_mode", + PTPD_RESTART_NETWORK, &rtOpts->ipMode, rtOpts->ipMode, + "IP transmission mode (requires IP transport) - hybrid mode uses\n" + " multicast for sync and announce, and unicast for delay request and\n" + " response; unicast mode uses unicast for all transmission.\n" + " When unicast mode is selected, destination IP(s) may need to be configured\n" + " (ptpengine:unicast_destinations).", + "multicast", IPMODE_MULTICAST, + "unicast", IPMODE_UNICAST, + "hybrid", IPMODE_HYBRID, NULL + ); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:unicast_negotiation", + PTPD_RESTART_PROTOCOL, &rtOpts->unicastNegotiation, rtOpts->unicastNegotiation, + "Enable unicast negotiation support using signaling messages\n"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:unicast_any_master", + PTPD_RESTART_NONE, &rtOpts->unicastAcceptAny, rtOpts->unicastAcceptAny, + "When using unicast negotiation (slave), accept PTP messages from any master.\n" + " By default, only messages from acceptable masters (ptpengine:unicast_destinations)\n" + " are accepted, and only if transmission was granted by the master\n"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:unicast_port_mask", + PTPD_RESTART_PROTOCOL, INTTYPE_U16, &rtOpts->unicastPortMask, rtOpts->unicastPortMask, + "PTP port number wildcard mask applied onto port identities when running\n" + " unicast negotiation: allows multiple port identities to be accepted as one.\n" + " This option can be used as a workaround where a node sends signaling messages and\n" + " timing messages with different port identities", RANGECHECK_RANGE, 0,65535); + + CONFIG_KEY_CONDITIONAL_WARNING_ISSET((rtOpts->transport == IEEE_802_3) && rtOpts->unicastNegotiation, + "ptpengine:unicast_negotiation", + "Unicast negotiation cannot be used with Ethernet transport\n"); + + CONFIG_KEY_CONDITIONAL_WARNING_ISSET((rtOpts->ipMode != IPMODE_UNICAST) && rtOpts->unicastNegotiation, + "ptpengine:unicast_negotiation", + "Unicast negotiation can only be used with unicast transmission\n"); + + /* disable unicast negotiation unless running unicast */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->transport == IEEE_802_3, rtOpts->unicastNegotiation,FALSE, rtOpts->unicastNegotiation); + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->ipMode != IPMODE_UNICAST, rtOpts->unicastNegotiation,FALSE, rtOpts->unicastNegotiation); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:disable_bmca", + PTPD_RESTART_PROTOCOL, &rtOpts->disableBMCA, rtOpts->disableBMCA, + "Disable Best Master Clock Algorithm for unicast masters:\n" + " Only effective for masteronly preset - all Announce messages\n" + " will be ignored and clock will transition directly into MASTER state.\n"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:unicast_negotiation_listening", + PTPD_RESTART_NONE, &rtOpts->unicastNegotiationListening, rtOpts->unicastNegotiationListening, + "When unicast negotiation enabled on a master clock, \n" + " reply to transmission requests also in LISTENING state."); + +#if defined(PTPD_PCAP) && defined(__sun) && !defined(PTPD_EXPERIMENTAL) + if(CONFIG_ISTRUE("ptpengine:use_libpcap")) + INFO("Libpcap support is currently marked broken/experimental on Solaris platforms.\n" + "To test it, please build with --enable-experimental-options\n"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:use_libpcap", + PTPD_RESTART_NETWORK, &rtOpts->pcap,FALSE, + "Use libpcap for sending and receiving traffic (automatically enabled\n" + " in Ethernet mode)."); + + /* cannot set ethernet transport without libpcap */ + CONFIG_KEY_VALUE_FORBIDDEN("ptpengine:transport", + rtOpts->transport == IEEE_802_3, + "ethernet", + "Libpcap support is currently marked broken/experimental on Solaris platforms.\n" + "To test it and use the Ethernet transport, please build with --enable-experimental-options\n"); +#elif defined(PTPD_PCAP) + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:use_libpcap", + PTPD_RESTART_NETWORK, &rtOpts->pcap, rtOpts->pcap, + "Use libpcap for sending and receiving traffic (automatically enabled\n" + " in Ethernet mode)."); + + /* in ethernet mode, activate pcap and overwrite previous setting */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->transport==IEEE_802_3, rtOpts->pcap,TRUE, rtOpts->pcap); +#else + if(CONFIG_ISTRUE("ptpengine:use_libpcap")) + INFO("Libpcap support disabled or not available. Please install libpcap,\n" + "build without --disable-pcap, or try building with ---with-pcap-config\n" + " to use ptpengine:use_libpcap.\n"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:use_libpcap", + PTPD_RESTART_NETWORK, &rtOpts->pcap,FALSE, + "Use libpcap for sending and receiving traffic (automatically enabled\n" + " in Ethernet mode)."); + + /* cannot set ethernet transport without libpcap */ + CONFIG_KEY_VALUE_FORBIDDEN("ptpengine:transport", + rtOpts->transport == IEEE_802_3, + "ethernet", + "Libpcap support disabled or not available. Please install libpcap,\n" + "build without --disable-pcap, or try building with ---with-pcap-config\n" + "to use Ethernet transport. "PTPD_PROGNAME" was built with no libpcap support.\n"); + +#endif /* PTPD_PCAP */ + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:disable_udp_checksums", + PTPD_RESTART_NETWORK, &rtOpts->disableUdpChecksums, rtOpts->disableUdpChecksums, + "Disable UDP checksum validation on UDP sockets (Linux only).\n" + " Workaround for situations where a node (like Transparent Clock).\n" + " does not rewrite checksums\n"); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:delay_mechanism", + PTPD_RESTART_PROTOCOL, &rtOpts->delayMechanism, rtOpts->delayMechanism, + "Delay detection mode used - use DELAY_DISABLED for syntonisation only\n" + " (no full synchronisation).", + delayMechToString(E2E), E2E, + delayMechToString(P2P), P2P, + delayMechToString(DELAY_DISABLED), DELAY_DISABLED, NULL + ); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:domain", + PTPD_RESTART_PROTOCOL, INTTYPE_U8, &rtOpts->domainNumber, rtOpts->domainNumber, + "PTP domain number.", RANGECHECK_RANGE, 0,127); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:port_number", PTPD_UPDATE_DATASETS, INTTYPE_U16, &rtOpts->portNumber, rtOpts->portNumber, + "PTP port number (part of PTP Port Identity - not UDP port).\n" + " For ordinary clocks (single port), the default should be used, \n" + " but when running multiple instances to simulate a boundary clock, \n" + " The port number can be changed.",RANGECHECK_RANGE,1,65534); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:port_description", + PTPD_UPDATE_DATASETS, rtOpts->portDescription, sizeof(rtOpts->portDescription), rtOpts->portDescription, + "Port description (returned in the userDescription field of PORT_DESCRIPTION management message and USER_DESCRIPTION" + " management message) - maximum 64 characters"); + + parseResult &= configMapString(opCode, opArg, dict, target, "variables:product_description", + PTPD_UPDATE_DATASETS, rtOpts->productDescription, sizeof(rtOpts->productDescription), rtOpts->productDescription,""); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:any_domain", + PTPD_RESTART_PROTOCOL, &rtOpts->anyDomain, rtOpts->anyDomain, + "Usability extension: if enabled, a slave-only clock will accept\n" + " masters from any domain, while preferring the configured domain,\n" + " and preferring lower domain number.\n" + " NOTE: this behaviour is not part of the standard."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:slave_only", + PTPD_RESTART_NONE, &rtOpts->slaveOnly, ptpPreset.slaveOnly, + "Slave only mode (sets clock class to 255, overriding value from preset)."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:inbound_latency", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->inboundLatency.nanoseconds, rtOpts->inboundLatency.nanoseconds, + "Specify latency correction (nanoseconds) for incoming packets.", RANGECHECK_NONE, 0,0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:outbound_latency", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->outboundLatency.nanoseconds, rtOpts->outboundLatency.nanoseconds, + "Specify latency correction (nanoseconds) for outgoing packets.", RANGECHECK_NONE,0,0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:offset_shift", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->ofmShift.nanoseconds, rtOpts->ofmShift.nanoseconds, + "Apply an arbitrary shift (nanoseconds) to offset from master when\n" + " in slave state. Value can be positive or negative - useful for\n" + " correcting for antenna latencies, delay assymetry\n" + " and IP stack latencies. This will not be visible in the offset \n" + " from master value - only in the resulting clock correction.", RANGECHECK_NONE, 0,0); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:always_respect_utc_offset", + PTPD_RESTART_NONE, &rtOpts->alwaysRespectUtcOffset, rtOpts->alwaysRespectUtcOffset, + "Compatibility option: In slave state, always respect UTC offset\n" + " announced by best master, even if the the\n" + " currrentUtcOffsetValid flag is announced FALSE.\n" + " NOTE: this behaviour is not part of the standard."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:prefer_utc_offset_valid", + PTPD_RESTART_NONE, &rtOpts->preferUtcValid, rtOpts->preferUtcValid, + "Compatibility extension to BMC algorithm: when enabled,\n" + " BMC for both master and save clocks will prefer masters\n" + " nannouncing currrentUtcOffsetValid as TRUE.\n" + " NOTE: this behaviour is not part of the standard."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:require_utc_offset_valid", + PTPD_RESTART_NONE, &rtOpts->requireUtcValid, rtOpts->requireUtcValid, + "Compatibility option: when enabled, ptpd2 will ignore\n" + " Announce messages from masters announcing currentUtcOffsetValid\n" + " as FALSE.\n" + " NOTE: this behaviour is not part of the standard."); + + /* from 30 seconds to 7 days */ + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:unicast_grant_duration", + PTPD_RESTART_PROTOCOL, INTTYPE_U32, &rtOpts->unicastGrantDuration, rtOpts->unicastGrantDuration, + "Time (seconds) unicast messages are requested for by slaves\n" + " when using unicast negotiation, and maximum time unicast message\n" + " transmission is granted to slaves by masters\n", RANGECHECK_RANGE, 30, 604800); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_announce_interval", PTPD_UPDATE_DATASETS, INTTYPE_I8, &rtOpts->logAnnounceInterval, rtOpts->logAnnounceInterval, + "PTP announce message interval in master state. When using unicast negotiation, for\n" + " slaves this is the minimum interval requested, and for masters\n" + " this is the only interval granted.\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-4,7); +#endif + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_announce_interval_max", PTPD_UPDATE_DATASETS, INTTYPE_I8, &rtOpts->logMaxAnnounceInterval, rtOpts->logMaxAnnounceInterval, + "Maximum Announce message interval requested by slaves " + "when using unicast negotiation,\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-1,7); +#endif + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->logAnnounceInterval >= rtOpts->logMaxAnnounceInterval, + "ptpengine:log_announce_interval value must be lower than ptpengine:log_announce_interval_max\n"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:announce_receipt_timeout", PTPD_UPDATE_DATASETS, INTTYPE_I8, &rtOpts->announceReceiptTimeout, rtOpts->announceReceiptTimeout, + "PTP announce receipt timeout announced in master state.",RANGECHECK_RANGE,2,255); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:announce_receipt_grace_period", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->announceTimeoutGracePeriod, rtOpts->announceTimeoutGracePeriod, + "PTP announce receipt timeout grace period in slave state:\n" + " when announce receipt timeout occurs, disqualify current best GM,\n" + " then wait n times announce receipt timeout before resetting.\n" + " Allows for a seamless GM failover when standby GMs are slow\n" + " to react. When set to 0, this option is not used.", RANGECHECK_RANGE, + 0,20); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:log_delayreq_override", PTPD_UPDATE_DATASETS, &rtOpts->ignore_delayreq_interval_master, + rtOpts->ignore_delayreq_interval_master, + "Override the Delay Request interval announced by best master."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:log_delayreq_auto", PTPD_UPDATE_DATASETS, &rtOpts->autoDelayReqInterval, + rtOpts->autoDelayReqInterval, + "Automatically override the Delay Request interval\n" + " if the announced value is 127 (0X7F), such as in\n" + " unicast messages (unless using unicast negotiation)"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_delayreq_interval_initial", + PTPD_RESTART_NONE, INTTYPE_I8, &rtOpts->initial_delayreq, rtOpts->initial_delayreq, + "Delay request interval used before receiving first delay response\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-7,7); +#endif + + /* take the delayreq_interval from config, otherwise use the initial setting as default */ + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_delayreq_interval", PTPD_UPDATE_DATASETS, INTTYPE_I8, &rtOpts->logMinDelayReqInterval, rtOpts->initial_delayreq, + "Minimum delay request interval announced when in master state,\n" + " in slave state overrides the master interval,\n" + " required in hybrid mode. When using unicast negotiation, for\n" + " slaves this is the minimum interval requested, and for masters\n" + " this is the minimum interval granted.\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-7,7); +#endif + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_delayreq_interval_max", PTPD_UPDATE_DATASETS, INTTYPE_I8, &rtOpts->logMaxDelayReqInterval, rtOpts->logMaxDelayReqInterval, + "Maximum Delay Response interval requested by slaves " + "when using unicast negotiation,\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-1,7); +#endif + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->logMinDelayReqInterval >= rtOpts->logMaxDelayReqInterval, + "ptpengine:log_delayreq_interval value must be lower than ptpengine:log_delayreq_interval_max\n"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_peer_delayreq_interval", + PTPD_RESTART_NONE, INTTYPE_I8, &rtOpts->logMinPdelayReqInterval, rtOpts->logMinPdelayReqInterval, + "Minimum peer delay request message interval in peer to peer delay mode.\n" + " When using unicast negotiation, this is the minimum interval requested, \n" + " and the only interval granted.\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-7,7); +#endif + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:log_peer_delayreq_interval_max", + PTPD_RESTART_NONE, INTTYPE_I8, &rtOpts->logMaxPdelayReqInterval, rtOpts->logMaxPdelayReqInterval, + "Maximum Peer Delay Response interval requested by slaves " + "when using unicast negotiation,\n" +#ifdef PTPD_EXPERIMENTAL + " "LOG2_HELP,RANGECHECK_RANGE,-30,30); +#else + " "LOG2_HELP,RANGECHECK_RANGE,-1,7); +#endif + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->logMinPdelayReqInterval >= rtOpts->logMaxPdelayReqInterval, + "ptpengine:log_peer_delayreq_interval value must be lower than ptpengine:log_peer_delayreq_interval_max\n"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:foreignrecord_capacity", + PTPD_RESTART_DAEMON, INTTYPE_I16, &rtOpts->max_foreign_records, rtOpts->max_foreign_records, + "Foreign master record size (Maximum number of foreign masters).",RANGECHECK_RANGE,5,10); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:ptp_allan_variance", PTPD_UPDATE_DATASETS, INTTYPE_U16, &rtOpts->clockQuality.offsetScaledLogVariance, rtOpts->clockQuality.offsetScaledLogVariance, + "Specify Allan variance announced in master state.",RANGECHECK_RANGE,0,65535); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:ptp_clock_accuracy", PTPD_UPDATE_DATASETS, &rtOpts->clockQuality.clockAccuracy, rtOpts->clockQuality.clockAccuracy, + "Clock accuracy range announced in master state.", + accToString(ACC_25NS), ACC_25NS, + accToString(ACC_100NS), ACC_100NS, + accToString(ACC_250NS), ACC_250NS, + accToString(ACC_1US), ACC_1US, + accToString(ACC_2_5US), ACC_2_5US, + accToString(ACC_10US), ACC_10US, + accToString(ACC_25US), ACC_25US, + accToString(ACC_100US), ACC_100US, + accToString(ACC_250US), ACC_250US, + accToString(ACC_1MS), ACC_1MS, + accToString(ACC_2_5MS), ACC_2_5MS, + accToString(ACC_10MS), ACC_10MS, + accToString(ACC_25MS), ACC_25MS, + accToString(ACC_100MS), ACC_100MS, + accToString(ACC_250MS), ACC_250MS, + accToString(ACC_1S), ACC_1S, + accToString(ACC_10S), ACC_10S, + accToString(ACC_10SPLUS), ACC_10SPLUS, + accToString(ACC_UNKNOWN), ACC_UNKNOWN, NULL + ); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:utc_offset", PTPD_UPDATE_DATASETS, INTTYPE_I16, &rtOpts->timeProperties.currentUtcOffset, rtOpts->timeProperties.currentUtcOffset, + "Underlying time source UTC offset announced in master state.", RANGECHECK_NONE,0,0); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:utc_offset_valid", PTPD_UPDATE_DATASETS, &rtOpts->timeProperties.currentUtcOffsetValid, + rtOpts->timeProperties.currentUtcOffsetValid, + "Underlying time source UTC offset validity announced in master state."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:time_traceable", PTPD_UPDATE_DATASETS, &rtOpts->timeProperties.timeTraceable, + rtOpts->timeProperties.timeTraceable, + "Underlying time source time traceability announced in master state."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:frequency_traceable", PTPD_UPDATE_DATASETS, &rtOpts->timeProperties.frequencyTraceable, + rtOpts->timeProperties.frequencyTraceable, + "Underlying time source frequency traceability announced in master state."); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:ptp_timescale", PTPD_UPDATE_DATASETS, (uint8_t*)&rtOpts->timeProperties.ptpTimescale, rtOpts->timeProperties.ptpTimescale, + "Time scale announced in master state (with ARB, UTC properties\n" + " are ignored by slaves). When clock class is set to 13 (application\n" + " specific), this value is ignored and ARB is used.", + "PTP", TRUE, + "ARB", FALSE, NULL + ); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:ptp_timesource", PTPD_UPDATE_DATASETS, &rtOpts->timeProperties.timeSource, rtOpts->timeProperties.timeSource, + "Time source announced in master state.", + "ATOMIC_CLOCK", ATOMIC_CLOCK, + "GPS", GPS, + "TERRESTRIAL_RADIO", TERRESTRIAL_RADIO, + "PTP", PTP, + "NTP", NTP, + "HAND_SET", HAND_SET, + "OTHER", OTHER, + "INTERNAL_OSCILLATOR", INTERNAL_OSCILLATOR, NULL + ); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:clock_class", PTPD_UPDATE_DATASETS, INTTYPE_U8, &rtOpts->clockQuality.clockClass,ptpPreset.clockClass.defaultValue, + "Clock class - announced in master state. Always 255 for slave-only.\n" + " Minimum, maximum and default values are controlled by presets.\n" + " If set to 13 (application specific time source), announced \n" + " time scale is always set to ARB. This setting controls the\n" + " states a PTP port can be in. If below 128, port will only\n" + " be in MASTER or PASSIVE states (master only). If above 127,\n" + " port will be in MASTER or SLAVE states.", RANGECHECK_RANGE, + ptpPreset.clockClass.minValue,ptpPreset.clockClass.maxValue); + + /* ClockClass = 13 triggers ARB */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->clockQuality.clockClass==DEFAULT_CLOCK_CLASS__APPLICATION_SPECIFIC_TIME_SOURCE, + rtOpts->timeProperties.ptpTimescale,FALSE, rtOpts->timeProperties.ptpTimescale); + + /* ClockClass = 14 triggers ARB */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->clockQuality.clockClass==14, + rtOpts->timeProperties.ptpTimescale,FALSE, rtOpts->timeProperties.ptpTimescale); + + /* ClockClass = 6 triggers PTP*/ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->clockQuality.clockClass==6, + rtOpts->timeProperties.ptpTimescale,TRUE, rtOpts->timeProperties.ptpTimescale); + + /* ClockClass = 7 triggers PTP*/ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->clockQuality.clockClass==7, + rtOpts->timeProperties.ptpTimescale,TRUE, rtOpts->timeProperties.ptpTimescale); + + /* ClockClass = 255 triggers slaveOnly */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->clockQuality.clockClass==SLAVE_ONLY_CLOCK_CLASS, rtOpts->slaveOnly,TRUE,FALSE); + /* ...and vice versa */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->slaveOnly==TRUE, rtOpts->clockQuality.clockClass,SLAVE_ONLY_CLOCK_CLASS, rtOpts->clockQuality.clockClass); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:priority1", PTPD_UPDATE_DATASETS, INTTYPE_U8, &rtOpts->priority1, rtOpts->priority1, + "Priority 1 announced in master state,used for Best Master\n" + " Clock selection.",RANGECHECK_RANGE,0,248); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:priority2", PTPD_UPDATE_DATASETS, INTTYPE_U8, &rtOpts->priority2, rtOpts->priority2, + "Priority 2 announced in master state, used for Best Master\n" + " Clock selection.",RANGECHECK_RANGE,0,248); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:max_listen", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->maxListen, rtOpts->maxListen, + "Number of consecutive resets to LISTENING before full network reset\n",RANGECHECK_MIN,1,0); + + /* + * TODO: in unicast and hybrid mode, automativally override master delayreq interval with a default, + * rather than require setting it manually. + */ + + /* hybrid mode -> should specify delayreq interval: override set in the bottom of this function */ + CONFIG_KEY_CONDITIONAL_WARNING_NOTSET(rtOpts->ipMode == IPMODE_HYBRID, + "ptpengine:log_delayreq_interval", + "It is recommended to manually set the delay request interval (ptpengine:log_delayreq_interval) to required value in hybrid mode" + ); + + /* unicast mode -> should specify delayreq interval if we can become a slave */ + CONFIG_KEY_CONDITIONAL_WARNING_NOTSET(rtOpts->ipMode == IPMODE_UNICAST && + rtOpts->clockQuality.clockClass > 127, + "ptpengine:log_delayreq_interval", + "It is recommended to manually set the delay request interval (ptpengine:log_delayreq_interval) to required value in unicast mode" + ); + + CONFIG_KEY_ALIAS("ptpengine:unicast_address","ptpengine:unicast_destinations"); + + /* unicast signaling slave -> must specify unicast destination(s) */ + CONFIG_KEY_CONDITIONAL_DEPENDENCY("ptpengine:ip_mode", + rtOpts->clockQuality.clockClass > 127 && + rtOpts->ipMode == IPMODE_UNICAST && + rtOpts->unicastNegotiation, + "unicast", + "ptpengine:unicast_destinations"); + + /* unicast master without signaling - must specify unicast destinations */ + CONFIG_KEY_CONDITIONAL_DEPENDENCY("ptpengine:ip_mode", + rtOpts->clockQuality.clockClass <= 127 && + rtOpts->ipMode == IPMODE_UNICAST && + !rtOpts->unicastNegotiation, + "unicast", + "ptpengine:unicast_destinations"); + + CONFIG_KEY_TRIGGER("ptpengine:unicast_destinations", rtOpts->unicastDestinationsSet,TRUE, rtOpts->unicastDestinationsSet); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:unicast_destinations", + PTPD_RESTART_NETWORK, rtOpts->unicastDestinations, sizeof(rtOpts->unicastDestinations), rtOpts->unicastDestinations, + "Specify unicast slave addresses for unicast master operation, or unicast\n" + " master addresses for slave operation. Format is similar to an ACL: comma,\n" + " tab or space-separated IPv4 unicast addresses, one or more. For a slave,\n" + " when unicast negotiation is used, setting this is mandatory."); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:unicast_domains", + PTPD_RESTART_NETWORK, rtOpts->unicastDomains, sizeof(rtOpts->unicastDomains), rtOpts->unicastDomains, + "Specify PTP domain number for each configured unicast destination (ptpengine:unicast_destinations).\n" + " This is only used by slave-only clocks using unicast destinations to allow for each master\n" + " to be in a separate domain, such as with Telecom Profile. The number of entries should match the number\n" + " of unicast destinations, otherwise unconfigured domains or domains set to 0 are set to domain configured in\n" + " ptpengine:domain. The format is a comma, tab or space-separated list of 8-bit unsigned integers (0 .. 255)"); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:unicast_local_preference", + PTPD_RESTART_NETWORK, rtOpts->unicastLocalPreference, sizeof(rtOpts->unicastLocalPreference), rtOpts->unicastLocalPreference, + "Specify a local preference for each configured unicast destination (ptpengine:unicast_destinations).\n" + " This is only used by slave-only clocks using unicast destinations to allow for each master's\n" + " BMC selection to be influenced by the slave, such as with Telecom Profile. The number of entries should match the number\n" + " of unicast destinations, otherwise unconfigured preference is set to 0 (highest).\n" + " The format is a comma, tab or space-separated list of 8-bit unsigned integers (0 .. 255)"); + + /* unicast P2P - must specify unicast peer destination */ + CONFIG_KEY_CONDITIONAL_DEPENDENCY("ptpengine:delay_mechanism", + rtOpts->delayMechanism == P2P && + rtOpts->ipMode == IPMODE_UNICAST, + "P2P", + "ptpengine:unicast_peer_destination"); + + CONFIG_KEY_TRIGGER("ptpengine:unicast_peer_destination", rtOpts->unicastPeerDestinationSet,TRUE, rtOpts->unicastPeerDestinationSet); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:unicast_peer_destination", + PTPD_RESTART_NETWORK, rtOpts->unicastPeerDestination, sizeof(rtOpts->unicastPeerDestination), rtOpts->unicastPeerDestination, + "Specify peer unicast adress for P2P unicast. Mandatory when\n" + " running unicast mode and P2P delay mode."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:management_enable", + PTPD_RESTART_NONE, &rtOpts->managementEnabled, rtOpts->managementEnabled, + "Enable handling of PTP management messages."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:management_set_enable", + PTPD_RESTART_NONE, &rtOpts->managementSetEnable, rtOpts->managementSetEnable, + "Accept SET and COMMAND management messages."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:igmp_refresh", + PTPD_RESTART_NONE, &rtOpts->do_IGMP_refresh, rtOpts->do_IGMP_refresh, + "Send explicit IGMP joins between engine resets and periodically\n" + " in master state."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:master_igmp_refresh_interval", + PTPD_RESTART_PROTOCOL, INTTYPE_I8, &rtOpts->masterRefreshInterval, rtOpts->masterRefreshInterval, + "Periodic IGMP join interval (seconds) in master state when running\n" + " IPv4 multicast: when set below 10 or when ptpengine:igmp_refresh\n" + " is disabled, this setting has no effect.",RANGECHECK_RANGE,0,255); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:multicast_ttl", + PTPD_RESTART_NETWORK, INTTYPE_INT, &rtOpts->ttl, rtOpts->ttl, + "Multicast time to live for multicast PTP packets (ignored and set to 1\n" + " for peer to peer messages).",RANGECHECK_RANGE,1,64); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:ip_dscp", + PTPD_RESTART_NETWORK, INTTYPE_INT, &rtOpts->dscpValue, rtOpts->dscpValue, + "DiffServ CodepPoint for packet prioritisation (decimal). When set to zero, \n" + " this option is not used. Use 46 for Expedited Forwarding (0x2e).",RANGECHECK_RANGE,0,63); + +#ifdef PTPD_STATISTICS + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:sync_stat_filter_enable", + PTPD_RESTART_FILTERS, &rtOpts->filterMSOpts.enabled, rtOpts->filterMSOpts.enabled, + "Enable statistical filter for Sync messages."); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:sync_stat_filter_type", + PTPD_RESTART_FILTERS, &rtOpts->filterMSOpts.filterType, rtOpts->filterMSOpts.filterType, + "Type of filter used for Sync message filtering", + "none", FILTER_NONE, + "mean", FILTER_MEAN, + "min", FILTER_MIN, + "max", FILTER_MAX, + "absmin", FILTER_ABSMIN, + "absmax", FILTER_ABSMAX, + "median", FILTER_MEDIAN, NULL); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_stat_filter_window", + PTPD_RESTART_FILTERS, INTTYPE_INT, &rtOpts->filterMSOpts.windowSize, rtOpts->filterMSOpts.windowSize, + "Number of samples used for the Sync statistical filter",RANGECHECK_RANGE,3,128); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:sync_stat_filter_window_type", + PTPD_RESTART_FILTERS, &rtOpts->filterMSOpts.windowType, rtOpts->filterMSOpts.windowType, + "Sample window type used for Sync message statistical filter. Delay Response outlier filter action.\n" + " Sliding window is continuous, interval passes every n-th sample only.", + "sliding", WINDOW_SLIDING, + "interval", WINDOW_INTERVAL, NULL); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:delay_stat_filter_enable", + PTPD_RESTART_FILTERS, &rtOpts->filterSMOpts.enabled, rtOpts->filterSMOpts.enabled, + "Enable statistical filter for Delay messages."); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:delay_stat_filter_type", + PTPD_RESTART_FILTERS, &rtOpts->filterSMOpts.filterType, rtOpts->filterSMOpts.filterType, + "Type of filter used for Delay message statistical filter", + "none", FILTER_NONE, + "mean", FILTER_MEAN, + "min", FILTER_MIN, + "max", FILTER_MAX, + "absmin", FILTER_ABSMIN, + "absmax", FILTER_ABSMAX, + "median", FILTER_MEDIAN, NULL); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_stat_filter_window", + PTPD_RESTART_FILTERS, INTTYPE_INT, &rtOpts->filterSMOpts.windowSize, rtOpts->filterSMOpts.windowSize, + "Number of samples used for the Delay statistical filter",RANGECHECK_RANGE,3,128); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:delay_stat_filter_window_type", + PTPD_RESTART_FILTERS, &rtOpts->filterSMOpts.windowType, rtOpts->filterSMOpts.windowType, + "Sample window type used for Delay message statistical filter\n" + " Sliding window is continuous, interval passes every n-th sample only", + "sliding", WINDOW_SLIDING, + "interval", WINDOW_INTERVAL, NULL); + + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_enable", + PTPD_RESTART_FILTERS, &rtOpts->oFilterSMConfig.enabled, rtOpts->oFilterSMConfig.enabled, + "Enable outlier filter for the Delay Response component in slave state"); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_action", + PTPD_RESTART_NONE, (uint8_t*)&rtOpts->oFilterSMConfig.discard, rtOpts->oFilterSMConfig.discard, + "Delay Response outlier filter action. If set to 'filter', outliers are\n" + " replaced with moving average.", + "discard", TRUE, + "filter", FALSE, NULL); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_capacity", + PTPD_RESTART_FILTERS, INTTYPE_INT, &rtOpts->oFilterSMConfig.capacity, rtOpts->oFilterSMConfig.capacity, + "Number of samples in the Delay Response outlier filter buffer",RANGECHECK_RANGE,10,STATCONTAINER_MAX_SAMPLES); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_threshold", + PTPD_RESTART_NONE, &rtOpts->oFilterSMConfig.threshold, rtOpts->oFilterSMConfig.threshold, + "Delay Response outlier filter threshold (: multiplier for Peirce's maximum\n" + " standard deviation. When set below 1.0, filter is tighter, when set above\n" + " 1.0, filter is looser than standard Peirce's test.\n" + " When autotune enabled, this is the starting threshold.", RANGECHECK_RANGE, 0.001, 1000.0); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_always_filter", + PTPD_RESTART_NONE, &rtOpts->oFilterSMConfig.alwaysFilter, rtOpts->oFilterSMConfig.alwaysFilter, + "Always run the Delay Response outlier filter, even if clock is being slewed at maximum rate"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_autotune_enable", + PTPD_RESTART_FILTERS, &rtOpts->oFilterSMConfig.autoTune, rtOpts->oFilterSMConfig.autoTune, + "Enable automatic threshold control for Delay Response outlier filter."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_autotune_minpercent", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->oFilterSMConfig.minPercent, rtOpts->oFilterSMConfig.minPercent, + "Delay Response outlier filter autotune low watermark - minimum percentage\n" + " of discarded samples in the update period before filter is tightened\n" + " by the autotune step value.",RANGECHECK_RANGE,0,99); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_autotune_maxpercent", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->oFilterSMConfig.maxPercent, rtOpts->oFilterSMConfig.maxPercent, + "Delay Response outlier filter autotune high watermark - maximum percentage\n" + " of discarded samples in the update period before filter is loosened\n" + " by the autotune step value.",RANGECHECK_RANGE,1,100); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:delay_outlier_autotune_step", + PTPD_RESTART_NONE, &rtOpts->oFilterSMConfig.thresholdStep, rtOpts->oFilterSMConfig.thresholdStep, + "The value the Delay Response outlier filter threshold is increased\n" + " or decreased by when auto-tuning.", RANGECHECK_RANGE, 0.01,10.0); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_autotune_minthreshold", + PTPD_RESTART_NONE, &rtOpts->oFilterSMConfig.minThreshold, rtOpts->oFilterSMConfig.minThreshold, + "Minimum Delay Response filter threshold value used when auto-tuning", RANGECHECK_RANGE, 0.01,10.0); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_autotune_maxthreshold", + PTPD_RESTART_NONE, &rtOpts->oFilterSMConfig.maxThreshold, rtOpts->oFilterSMConfig.maxThreshold, + "Maximum Delay Response filter threshold value used when auto-tuning", RANGECHECK_RANGE, 0.01,10.0); + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->oFilterSMConfig.maxPercent <= rtOpts->oFilterSMConfig.minPercent, + "ptpengine:delay_outlier_filter_autotune_maxpercent value has to be greater " + "than ptpengine:delay_outlier_filter_autotune_minpercent\n"); + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->oFilterSMConfig.maxThreshold <= rtOpts->oFilterSMConfig.minThreshold, + "ptpengine:delay_outlier_filter_autotune_maxthreshold value has to be greater " + "than ptpengine:delay_outlier_filter_autotune_minthreshold\n"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_stepdetect_enable", + PTPD_RESTART_FILTERS, &rtOpts->oFilterSMConfig.stepDelay, rtOpts->oFilterSMConfig.stepDelay, + "Enable Delay filter step detection (delaySM) to block when certain level exceeded"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_stepdetect_threshold", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->oFilterSMConfig.stepThreshold, rtOpts->oFilterSMConfig.stepThreshold, + "Delay Response step detection threshold. Step detection is performed\n" + " only when delaySM is below this threshold (nanoseconds)", RANGECHECK_RANGE, 50000, NANOSECONDS_MAX); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_stepdetect_level", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->oFilterSMConfig.stepLevel, rtOpts->oFilterSMConfig.stepLevel, + "Delay Response step level. When step detection enabled and operational,\n" + " delaySM above this level (nanosecond) is considered a clock step and updates are paused", RANGECHECK_RANGE,50000, NANOSECONDS_MAX); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_stepdetect_credit", + PTPD_RESTART_FILTERS, INTTYPE_I32, &rtOpts->oFilterSMConfig.delayCredit, rtOpts->oFilterSMConfig.delayCredit, + "Initial credit (number of samples) the Delay step detection filter can block for\n" + " When credit is exhausted, filter stops blocking. Credit is gradually restored",RANGECHECK_RANGE,50,1000); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:delay_outlier_filter_stepdetect_credit_increment", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->oFilterSMConfig.creditIncrement, rtOpts->oFilterSMConfig.creditIncrement, + "Amount of credit for the Delay step detection filter restored every full sample window",RANGECHECK_RANGE,1,100); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:delay_outlier_weight", + PTPD_RESTART_NONE, &rtOpts->oFilterSMConfig.weight, rtOpts->oFilterSMConfig.weight, + "Delay Response outlier weight: if an outlier is detected, determines\n" + " the amount of its deviation from mean that is used to build the standard\n" + " deviation statistics and influence further outlier detection.\n" + " When set to 1.0, the outlier is used as is.", RANGECHECK_RANGE, 0.01, 2.0); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_enable", + PTPD_RESTART_FILTERS, &rtOpts->oFilterMSConfig.enabled, rtOpts->oFilterMSConfig.enabled, + "Enable outlier filter for the Sync component in slave state."); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_action", + PTPD_RESTART_NONE, (uint8_t*)&rtOpts->oFilterMSConfig.discard, rtOpts->oFilterMSConfig.discard, + "Sync outlier filter action. If set to 'filter', outliers are replaced\n" + " with moving average.", + "discard", TRUE, + "filter", FALSE, NULL); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_capacity", + PTPD_RESTART_FILTERS, INTTYPE_INT, &rtOpts->oFilterMSConfig.capacity, rtOpts->oFilterMSConfig.capacity, + "Number of samples in the Sync outlier filter buffer.",RANGECHECK_RANGE,10,STATCONTAINER_MAX_SAMPLES); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_threshold", + PTPD_RESTART_NONE, &rtOpts->oFilterMSConfig.threshold, rtOpts->oFilterMSConfig.threshold, + "Sync outlier filter threshold: multiplier for the Peirce's maximum standard\n" + " deviation. When set below 1.0, filter is tighter, when set above 1.0,\n" + " filter is looser than standard Peirce's test.", RANGECHECK_RANGE, 0.001, 1000.0); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_always_filter", + PTPD_RESTART_NONE, &rtOpts->oFilterMSConfig.alwaysFilter, rtOpts->oFilterMSConfig.alwaysFilter, + "Always run the Sync outlier filter, even if clock is being slewed at maximum rate"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_autotune_enable", + PTPD_RESTART_FILTERS, &rtOpts->oFilterMSConfig.autoTune, rtOpts->oFilterMSConfig.autoTune, + "Enable automatic threshold control for Sync outlier filter."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_autotune_minpercent", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->oFilterMSConfig.minPercent, rtOpts->oFilterMSConfig.minPercent, + "Sync outlier filter autotune low watermark - minimum percentage\n" + " of discarded samples in the update period before filter is tightened\n" + " by the autotune step value.",RANGECHECK_RANGE,0,99); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_autotune_maxpercent", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->oFilterMSConfig.maxPercent, rtOpts->oFilterMSConfig.maxPercent, + "Sync outlier filter autotune high watermark - maximum percentage\n" + " of discarded samples in the update period before filter is loosened\n" + " by the autotune step value.",RANGECHECK_RANGE,1,100); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:sync_outlier_autotune_step", + PTPD_RESTART_NONE, &rtOpts->oFilterMSConfig.thresholdStep, rtOpts->oFilterMSConfig.thresholdStep, + "Value the Sync outlier filter threshold is increased\n" + " or decreased by when auto-tuning.", RANGECHECK_RANGE, 0.01,10.0); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_autotune_minthreshold", + PTPD_RESTART_NONE, &rtOpts->oFilterMSConfig.minThreshold, rtOpts->oFilterMSConfig.minThreshold, + "Minimum Sync outlier filter threshold value used when auto-tuning", RANGECHECK_RANGE, 0.01,10.0); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_autotune_maxthreshold", + PTPD_RESTART_NONE, &rtOpts->oFilterMSConfig.maxThreshold, rtOpts->oFilterMSConfig.maxThreshold, + "Maximum Sync outlier filter threshold value used when auto-tuning", RANGECHECK_RANGE, 0.01,10.0); + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->oFilterMSConfig.maxPercent <= rtOpts->oFilterMSConfig.minPercent, + "ptpengine:sync_outlier_filter_autotune_maxpercent value has to be greater " + "than ptpengine:sync_outlier_filter_autotune_minpercent\n"); + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->oFilterMSConfig.maxThreshold <= rtOpts->oFilterMSConfig.minThreshold, + "ptpengine:sync_outlier_filter_autotune_maxthreshold value has to be greater " + "than ptpengine:sync_outlier_filter_autotune_minthreshold\n"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_stepdetect_enable", + PTPD_RESTART_FILTERS, &rtOpts->oFilterMSConfig.stepDelay, rtOpts->oFilterMSConfig.stepDelay, + "Enable Sync filter step detection (delayMS) to block when certain level exceeded."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_stepdetect_threshold", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->oFilterMSConfig.stepThreshold, rtOpts->oFilterMSConfig.stepThreshold, + "Sync step detection threshold. Step detection is performed\n" + " only when delayMS is below this threshold (nanoseconds)", RANGECHECK_RANGE,100000, NANOSECONDS_MAX); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_stepdetect_level", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->oFilterMSConfig.stepLevel, rtOpts->oFilterMSConfig.stepLevel, + "Sync step level. When step detection enabled and operational,\n" + " delayMS above this level (nanosecond) is considered a clock step and updates are paused", RANGECHECK_RANGE,100000, NANOSECONDS_MAX); + + CONFIG_CONDITIONAL_ASSERTION(rtOpts->oFilterMSConfig.stepThreshold <= (rtOpts->oFilterMSConfig.stepLevel + 100000), + "ptpengine:sync_outlier_filter_stepdetect_threshold has to be at least " + "100 us (100000) greater than ptpengine:sync_outlier_filter_stepdetect_level\n"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_stepdetect_credit", + PTPD_RESTART_FILTERS, INTTYPE_INT, &rtOpts->oFilterMSConfig.delayCredit, rtOpts->oFilterMSConfig.delayCredit, + "Initial credit (number of samples) the Sync step detection filter can block for.\n" + " When credit is exhausted, filter stops blocking. Credit is gradually restored",RANGECHECK_RANGE,50,1000); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:sync_outlier_filter_stepdetect_credit_increment", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->oFilterMSConfig.creditIncrement, rtOpts->oFilterMSConfig.creditIncrement, + "Amount of credit for the Sync step detection filter restored every full sample window",RANGECHECK_RANGE,1,100); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "ptpengine:sync_outlier_weight", + PTPD_RESTART_NONE, &rtOpts->oFilterMSConfig.weight, rtOpts->oFilterMSConfig.weight, + "Sync outlier weight: if an outlier is detected, this value determines the\n" + " amount of its deviation from mean that is used to build the standard \n" + " deviation statistics and influence further outlier detection.\n" + " When set to 1.0, the outlier is used as is.", RANGECHECK_RANGE, 0.01, 2.0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:calibration_delay", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->calibrationDelay, rtOpts->calibrationDelay, + "Delay between moving to slave state and enabling clock updates (seconds).\n" + " This allows one-way delay to stabilise before starting clock updates.\n" + " Activated when going into slave state and during slave's GM failover.\n" + " 0 - not used.",RANGECHECK_RANGE,0,300); + +#endif /* PTPD_STATISTICS */ + + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:idle_timeout", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->idleTimeout, rtOpts->idleTimeout, + "PTP idle timeout: if PTPd is in SLAVE state and there have been no clock\n" + " updates for this amout of time, PTPd releases clock control.\n", RANGECHECK_RANGE,10,3600); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:offset_alarm_threshold", PTPD_UPDATE_DATASETS, INTTYPE_U32, &rtOpts->ofmAlarmThreshold, rtOpts->ofmAlarmThreshold, + "PTP slave offset from master threshold (nanoseconds - absolute value)\n" + " When offset exceeds this value, an alarm is raised (also SNMP trap if configured).\n" + " 0 = disabled.", RANGECHECK_NONE,0,NANOSECONDS_MAX); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:panic_mode", + PTPD_RESTART_NONE, &rtOpts->enablePanicMode, rtOpts->enablePanicMode, + "Enable panic mode: when offset from master is above 1 second, stop updating\n" + " the clock for a period of time and then step the clock if offset remains\n" + " above 1 second."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:panic_mode_duration", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->panicModeDuration, rtOpts->panicModeDuration, + "Duration (minutes) of the panic mode period (no clock updates) when offset\n" + " above 1 second detected.",RANGECHECK_RANGE,1,60); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:panic_mode_release_clock", + PTPD_RESTART_NONE, &rtOpts->panicModeReleaseClock, rtOpts->panicModeReleaseClock, + "When entering panic mode, release clock control while panic mode lasts\n" + " if ntpengine:* configured, this will fail over to NTP,\n" + " if not set, PTP will hold clock control during panic mode."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:panic_mode_exit_threshold", + PTPD_RESTART_NONE, INTTYPE_U32, &rtOpts->panicModeExitThreshold, rtOpts->panicModeExitThreshold, + "Do not exit panic mode until offset drops below this value (nanoseconds).\n" + " 0 = not used.",RANGECHECK_RANGE,0,NANOSECONDS_MAX); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:pid_as_clock_identity", + PTPD_RESTART_PROTOCOL, &rtOpts->pidAsClockId, rtOpts->pidAsClockId, + "Use PTPd's process ID as the middle part of the PTP clock ID - useful for running multiple instances."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:ntp_failover", + PTPD_RESTART_NONE, &rtOpts->ntpOptions.enableFailover, rtOpts->ntpOptions.enableFailover, + "Fail over to NTP when PTP time sync not available - requires\n" + " ntpengine:enabled, but does not require the rest of NTP configuration:\n" + " will warn instead of failing over if cannot control ntpd."); + + CONFIG_KEY_DEPENDENCY("ptpengine:ntp_failover", "ntpengine:enabled"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:ntp_failover_timeout", + PTPD_RESTART_NTPCONFIG, INTTYPE_INT, &rtOpts->ntpOptions.failoverTimeout, + rtOpts->ntpOptions.failoverTimeout, + "NTP failover timeout in seconds: time between PTP slave going into\n" + " LISTENING state, and releasing clock control. 0 = fail over immediately.", RANGECHECK_RANGE,0, 1800); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:prefer_ntp", + PTPD_RESTART_NTPCONFIG, &rtOpts->preferNTP, rtOpts->preferNTP, + "Prefer NTP time synchronisation. Only use PTP when NTP not available,\n" + " could be used when NTP runs with a local GPS receiver or another reference"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:panic_mode_ntp", + PTPD_RESTART_NONE, &rtOpts->panicModeReleaseClock, rtOpts->panicModeReleaseClock, + "Legacy option from 2.3.0: same as ptpengine:panic_mode_release_clock"); + + CONFIG_KEY_DEPENDENCY("ptpengine:panic_mode_ntp", "ntpengine:enabled"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ptpengine:sigusr2_clears_counters", + PTPD_RESTART_NONE, &rtOpts->clearCounters, rtOpts->clearCounters, + "Clear counters after dumping all counter values on SIGUSR2."); + + /* Defining the ACLs enables ACL matching */ + CONFIG_KEY_TRIGGER("ptpengine:timing_acl_permit", rtOpts->timingAclEnabled,TRUE, rtOpts->timingAclEnabled); + CONFIG_KEY_TRIGGER("ptpengine:timing_acl_deny", rtOpts->timingAclEnabled,TRUE, rtOpts->timingAclEnabled); + CONFIG_KEY_TRIGGER("ptpengine:management_acl_permit", rtOpts->managementAclEnabled,TRUE, rtOpts->managementAclEnabled); + CONFIG_KEY_TRIGGER("ptpengine:management_acl_deny", rtOpts->managementAclEnabled,TRUE, rtOpts->managementAclEnabled); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:timing_acl_permit", + PTPD_RESTART_ACLS, rtOpts->timingAclPermitText, sizeof(rtOpts->timingAclPermitText), rtOpts->timingAclPermitText, + "Permit access control list for timing packets. Format is a series of \n" + " comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x,\n" + " where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the\n" + " subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the\n" + " incoming messages. IP access lists are only supported when using the IP transport."); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:timing_acl_deny", + PTPD_RESTART_ACLS, rtOpts->timingAclDenyText, sizeof(rtOpts->timingAclDenyText), rtOpts->timingAclDenyText, + "Deny access control list for timing packets. Format is a series of \n" + " comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x,\n" + " where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the\n" + " subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the\n" + " incoming messages. IP access lists are only supported when using the IP transport."); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:management_acl_permit", + PTPD_RESTART_ACLS, rtOpts->managementAclPermitText, sizeof(rtOpts->managementAclPermitText), rtOpts->managementAclPermitText, + "Permit access control list for management messages. Format is a series of \n" + " comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x,\n" + " where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the\n" + " subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the\n" + " incoming messages. IP access lists are only supported when using the IP transport."); + + parseResult &= configMapString(opCode, opArg, dict, target, "ptpengine:management_acl_deny", + PTPD_RESTART_ACLS, rtOpts->managementAclDenyText, sizeof(rtOpts->managementAclDenyText), rtOpts->managementAclDenyText, + "Deny access control list for management messages. Format is a series of \n" + " comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x,\n" + " where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the\n" + " subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the\n" + " incoming messages. IP access lists are only supported when using the IP transport."); + + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:timing_acl_order", + PTPD_RESTART_ACLS, &rtOpts->timingAclOrder, rtOpts->timingAclOrder, + "Order in which permit and deny access lists are evaluated for timing\n" + " packets, the evaluation process is the same as for Apache httpd.", + "permit-deny", ACL_PERMIT_DENY, + "deny-permit", ACL_DENY_PERMIT, NULL + ); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "ptpengine:management_acl_order", + PTPD_RESTART_ACLS, &rtOpts->managementAclOrder, rtOpts->managementAclOrder, + "Order in which permit and deny access lists are evaluated for management\n" + " messages, the evaluation process is the same as for Apache httpd.", + "permit-deny", ACL_PERMIT_DENY, + "deny-permit", ACL_DENY_PERMIT, NULL + ); + + + /* Ethernet mode disables ACL processing*/ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->transport == IEEE_802_3, rtOpts->timingAclEnabled,FALSE, rtOpts->timingAclEnabled); + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->transport == IEEE_802_3, rtOpts->managementAclEnabled,FALSE, rtOpts->managementAclEnabled); + + + +/* ===== clock section ===== */ + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "clock:no_adjust", + PTPD_RESTART_NONE, &rtOpts->noAdjust,ptpPreset.noAdjust, + "Do not adjust the clock"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "clock:no_reset", + PTPD_RESTART_NONE, &rtOpts->noResetClock, rtOpts->noResetClock, + "Do not step the clock - only slew"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "clock:step_startup_force", + PTPD_RESTART_NONE, &rtOpts->stepForce, rtOpts->stepForce, + "Force clock step on first sync after startup regardless of offset and clock:no_reset"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "clock:step_startup", + PTPD_RESTART_NONE, &rtOpts->stepOnce, rtOpts->stepOnce, + "Step clock on startup if offset >= 1 second, ignoring\n" + " panic mode and clock:no_reset"); + + +#ifdef HAVE_LINUX_RTC_H + parseResult &= configMapBoolean(opCode, opArg, dict, target, "clock:set_rtc_on_step", + PTPD_RESTART_NONE, &rtOpts->setRtc, rtOpts->setRtc, + "Attempt setting the RTC when stepping clock (Linux only - FreeBSD does \n" + " this for us. WARNING: this will always set the RTC to OS clock time,\n" + " regardless of time zones, so this assumes that RTC runs in UTC or \n" + " at least in the same timescale as PTP. true at least on most \n" + " single-boot x86 Linux systems."); +#endif /* HAVE_LINUX_RTC_H */ + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "clock:drift_handling", + PTPD_RESTART_NONE, &rtOpts->drift_recovery_method, rtOpts->drift_recovery_method, + "Observed drift handling method between servo restarts:\n" + " reset: set to zero (not recommended)\n" + " preserve: use kernel value,\n" + " file: load/save to drift file on startup/shutdown, use kernel\n" + " value inbetween. To specify drift file, use the clock:drift_file setting." + , + "reset", DRIFT_RESET, + "preserve", DRIFT_KERNEL, + "file", DRIFT_FILE, NULL + ); + + parseResult &= configMapString(opCode, opArg, dict, target, "clock:drift_file", + PTPD_RESTART_NONE, rtOpts->driftFile, sizeof(rtOpts->driftFile), rtOpts->driftFile, + "Specify drift file"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "clock:leap_second_pause_period", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->leapSecondPausePeriod, + rtOpts->leapSecondPausePeriod, + "Time (seconds) before and after midnight that clock updates should pe suspended for\n" + " during a leap second event. The total duration of the pause is twice\n" + " the configured duration",RANGECHECK_RANGE,5,600); + + parseResult &= configMapInt(opCode, opArg, dict, target, "clock:leap_second_notice_period", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->leapSecondNoticePeriod, + rtOpts->leapSecondNoticePeriod, + "Time (seconds) before midnight that PTPd starts announcing the leap second\n" + " if it's running as master",RANGECHECK_RANGE,3600,86400); + + parseResult &= configMapString(opCode, opArg, dict, target, "clock:leap_seconds_file", + PTPD_RESTART_NONE, rtOpts->leapFile, sizeof(rtOpts->leapFile), rtOpts->leapFile, + "Specify leap second file location - up to date version can be downloaded from \n" + " http://www.ietf.org/timezones/data/leap-seconds.list"); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "clock:leap_second_handling", + PTPD_RESTART_NONE, &rtOpts->leapSecondHandling, rtOpts->leapSecondHandling, + "Behaviour during a leap second event:\n" + " accept: inform the OS kernel of the event\n" + " ignore: do nothing - ends up with a 1-second offset which is then slewed\n" + " step: similar to ignore, but steps the clock immediately after the leap second event\n" + " smear: do not inform kernel, gradually introduce the leap second before the event\n" + " by modifying clock offset (see clock:leap_second_smear_period)", + "accept", LEAP_ACCEPT, + "ignore", LEAP_IGNORE, + "step", LEAP_STEP, + "smear", LEAP_SMEAR, NULL + ); + + parseResult &= configMapInt(opCode, opArg, dict, target, "clock:leap_second_smear_period", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->leapSecondSmearPeriod, + rtOpts->leapSecondSmearPeriod, + "Time period (Seconds) over which the leap second is introduced before the event.\n" + " Example: when set to 86400 (24 hours), an extra 11.5 microseconds is added every second" + ,RANGECHECK_RANGE,3600,86400); + + +#ifdef HAVE_STRUCT_TIMEX_TICK + /* This really is clock specific - different clocks may allow different ranges */ + parseResult &= configMapInt(opCode, opArg, dict, target, "clock:max_offset_ppm", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->servoMaxPpb, rtOpts->servoMaxPpb, + "Maximum absolute frequency shift which can be applied to the clock servo\n" + " when slewing the clock. Expressed in parts per million (1 ppm = shift of\n" + " 1 us per second. Values above 512 will use the tick duration correction\n" + " to allow even faster slewing. Default maximum is 512 without using tick.", RANGECHECK_RANGE, + ADJ_FREQ_MAX/1000,ADJ_FREQ_MAX/500); +#endif /* HAVE_STRUCT_TIMEX_TICK */ + + /* + * TimeProperties DS - in future when clock driver API is implemented, + * a slave PTP engine should inform a clock about this, and then that + * clock should pass this information to any master PTP engines, unless + * we override this. here. For now we just supply this to RtOpts. + */ + + +/* ===== servo section ===== */ + + parseResult &= configMapInt(opCode, opArg, dict, target, "servo:delayfilter_stiffness", + PTPD_RESTART_NONE, INTTYPE_I16, &rtOpts->s, rtOpts->s, + "One-way delay filter stiffness.", RANGECHECK_NONE,0,0); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "servo:kp", + PTPD_RESTART_NONE, &rtOpts->servoKP, rtOpts->servoKP, + "Clock servo PI controller proportional component gain (kP).", RANGECHECK_MIN, 0.000001, 0); + parseResult &= configMapDouble(opCode, opArg, dict, target, "servo:ki", + PTPD_RESTART_NONE, &rtOpts->servoKI, rtOpts->servoKI, + "Clock servo PI controller integral component gain (kI).", RANGECHECK_MIN, 0.000001,0); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "servo:dt_method", + PTPD_RESTART_NONE, &rtOpts->servoDtMethod, rtOpts->servoDtMethod, + "How servo update interval (delta t) is calculated:\n" + " none: servo not corrected for update interval (dt always 1),\n" + " constant: constant value (target servo update rate - sync interval for PTP,\n" + " measured: servo measures how often it's updated and uses this interval.", + "none", DT_NONE, + "constant", DT_CONSTANT, + "measured", DT_MEASURED, NULL + ); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "servo:dt_max", + PTPD_RESTART_NONE, &rtOpts->servoMaxdT, rtOpts->servoMaxdT, + "Maximum servo update interval (delta t) when using measured servo update interval\n" + " (servo:dt_method = measured), specified as sync interval multiplier.", RANGECHECK_RANGE, 1.5,100.0); + +#ifdef PTPD_STATISTICS + parseResult &= configMapBoolean(opCode, opArg, dict, target, "servo:stability_detection", + PTPD_RESTART_NONE, &rtOpts->servoStabilityDetection, + rtOpts->servoStabilityDetection, + "Enable clock synchronisation servo stability detection\n" + " (based on standard deviation of the observed drift value)\n" + " - drift will be saved to drift file / cached when considered stable,\n" + " also clock stability status will be logged."); + + parseResult &= configMapDouble(opCode, opArg, dict, target, "servo:stability_threshold", + PTPD_RESTART_NONE, &rtOpts->servoStabilityThreshold, + rtOpts->servoStabilityThreshold, + "Specify the observed drift standard deviation threshold in parts per\n" + " billion (ppb) - if stanard deviation is within the threshold, servo\n" + " is considered stable.", RANGECHECK_RANGE, 1.0,10000.0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "servo:stability_period", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->servoStabilityPeriod, + rtOpts->servoStabilityPeriod, + "Specify for how many statistics update intervals the observed drift\n" + " standard deviation has to stay within threshold to be considered stable.", RANGECHECK_RANGE, + 1,100); + + parseResult &= configMapInt(opCode, opArg, dict, target, "servo:stability_timeout", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->servoStabilityTimeout, + rtOpts->servoStabilityTimeout, + "Specify after how many minutes without stabilisation servo is considered\n" + " unstable. Assists with logging servo stability information and\n" + " allows to preserve observed drift if servo cannot stabilise.\n", RANGECHECK_RANGE, + 1,60); + +#endif + + parseResult &= configMapInt(opCode, opArg, dict, target, "servo:max_delay", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->maxDelay, rtOpts->maxDelay, + "Do accept master to slave delay (delayMS - from Sync message) or slave to master delay\n" + " (delaySM - from Delay messages) if greater than this value (nanoseconds). 0 = not used.", RANGECHECK_RANGE, + 0,NANOSECONDS_MAX); + + parseResult &= configMapInt(opCode, opArg, dict, target, "servo:max_delay_max_rejected", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->maxDelayMaxRejected, rtOpts->maxDelayMaxRejected, + "Maximum number of consecutive delay measurements exceeding maxDelay threshold,\n" + " before slave is reset.", RANGECHECK_MIN,0,0); + +#ifdef PTPD_STATISTICS + parseResult &= configMapBoolean(opCode, opArg, dict, target, "servo:max_delay_stable_only", + PTPD_RESTART_NONE, &rtOpts->maxDelayStableOnly, rtOpts->maxDelayStableOnly, + "If servo:max_delay is set, perform the check only if clock servo has stabilised.\n"); +#endif + + parseResult &= configMapInt(opCode, opArg, dict, target, "ptpengine:clock_update_timeout", + PTPD_RESTART_PROTOCOL, INTTYPE_INT, &rtOpts->clockUpdateTimeout, rtOpts->clockUpdateTimeout, + "If set to non-zero, timeout in seconds, after which the slave resets if no clock updates made. \n", RANGECHECK_RANGE, + 0, 3600); + + parseResult &= configMapInt(opCode, opArg, dict, target, "servo:max_offset", + PTPD_RESTART_NONE, INTTYPE_I32, &rtOpts->maxOffset, rtOpts->maxOffset, + "Do not reset the clock if offset from master is greater\n" + " than this value (nanoseconds). 0 = not used.", RANGECHECK_RANGE, + 0,NANOSECONDS_MAX); + +/* ===== global section ===== */ + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:enable_alarms", + PTPD_RESTART_ALARMS, &rtOpts->alarmsEnabled, rtOpts->alarmsEnabled, + "Enable support for alarm and event notifications.\n"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:alarm_timeout", + PTPD_RESTART_ALARMS, INTTYPE_INT, &rtOpts->alarmMinAge, rtOpts->alarmMinAge, + "Mininmum alarm age (seconds) - minimal time between alarm set and clear notifications.\n" + " The condition can clear while alarm lasts, but notification (log or SNMP) will only \n" + " be triggered after the timeout. This option prevents from alarms flapping.", RANGECHECK_RANGE, 0, 3600); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:alarm_initial_delay", + PTPD_RESTART_DAEMON, INTTYPE_INT, &rtOpts->alarmInitialDelay, rtOpts->alarmInitialDelay, + "Delay the start of alarm processing (seconds) after ptpd startup. This option \n" + " allows to avoid unnecessary alarms before PTPd starts synchronising.\n", + RANGECHECK_RANGE, 0, 3600); + +#ifdef PTPD_SNMP + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:enable_snmp", + PTPD_RESTART_DAEMON, &rtOpts->snmpEnabled, rtOpts->snmpEnabled, + "Enable SNMP agent (if compiled with PTPD_SNMP)."); + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:enable_snmp_traps", + PTPD_RESTART_ALARMS, &rtOpts->snmpTrapsEnabled, rtOpts->snmpTrapsEnabled, + "Enable sending SNMP traps (only if global:enable_alarms set and global:enable_snmp set).\n"); +#else + if(!(opCode & CFGOP_PARSE_QUIET) && CONFIG_ISTRUE("global:enable_snmp")) + INFO("SNMP support not enabled. Please compile with PTPD_SNMP to use global:enable_snmp\n"); + if(!(opCode & CFGOP_PARSE_QUIET) && CONFIG_ISTRUE("global:enable_snmp_traps")) + INFO("SNMP support not enabled. Please compile with PTPD_SNMP to use global:enable_snmp_traps\n"); +#endif /* PTPD_SNMP */ + + + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:use_syslog", + PTPD_RESTART_LOGGING, &rtOpts->useSysLog, rtOpts->useSysLog, + "Send log messages to syslog. Disabling this\n" + " sends all messages to stdout (or speficied log file)."); + + parseResult &= configMapString(opCode, opArg, dict, target, "global:lock_file", + PTPD_RESTART_DAEMON, rtOpts->lockFile, sizeof(rtOpts->lockFile), rtOpts->lockFile, + "Lock file location"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:auto_lockfile", + PTPD_RESTART_DAEMON, &rtOpts->autoLockFile, rtOpts->autoLockFile, + " Use mode specific and interface specific lock file\n" + " (overrides global:lock_file)."); + + parseResult &= configMapString(opCode, opArg, dict, target, "global:lock_directory", + PTPD_RESTART_DAEMON, rtOpts->lockDirectory, sizeof(rtOpts->lockDirectory), rtOpts->lockDirectory, + "Lock file directory: used with automatic mode-specific lock files,\n" + " also used when no lock file is specified. When lock file\n" + " is specified, it's expected to be an absolute path."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:ignore_lock", + PTPD_RESTART_DAEMON, &rtOpts->ignore_daemon_lock, rtOpts->ignore_daemon_lock, + "Skip lock file checking and locking."); + + /* if quality file specified, enable quality recording */ + CONFIG_KEY_TRIGGER("global:quality_file", rtOpts->recordLog.logEnabled,TRUE,FALSE); + parseResult &= configMapString(opCode, opArg, dict, target, "global:quality_file", + PTPD_RESTART_LOGGING, rtOpts->recordLog.logPath, sizeof(rtOpts->recordLog.logPath), rtOpts->recordLog.logPath, + "File used to record data about sync packets. Enables recording when set."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:quality_file_max_size", + PTPD_RESTART_LOGGING, INTTYPE_U32, &rtOpts->recordLog.maxSize, rtOpts->recordLog.maxSize, + "Maximum sync packet record file size (in kB) - file will be truncated\n" + " if size exceeds the limit. 0 - no limit.", RANGECHECK_MIN,0,0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:quality_file_max_files", + PTPD_RESTART_LOGGING, INTTYPE_INT, &rtOpts->recordLog.maxFiles, rtOpts->recordLog.maxFiles, + "Enable log rotation of the sync packet record file up to n files.\n" + " 0 - do not rotate.\n", RANGECHECK_RANGE,0, 100); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:quality_file_truncate", + PTPD_RESTART_LOGGING, &rtOpts->recordLog.truncateOnReopen, rtOpts->recordLog.truncateOnReopen, + "Truncate the sync packet record file every time it is (re) opened:\n" + " startup and SIGHUP."); + + /* if status file specified, enable status logging*/ + CONFIG_KEY_TRIGGER("global:status_file", rtOpts->statusLog.logEnabled,TRUE,FALSE); + parseResult &= configMapString(opCode, opArg, dict, target, "global:status_file", + PTPD_RESTART_LOGGING, rtOpts->statusLog.logPath, sizeof(rtOpts->statusLog.logPath), rtOpts->statusLog.logPath, + "File used to log "PTPD_PROGNAME" status information."); + /* status file can be disabled even if specified */ + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:log_status", + PTPD_RESTART_NONE, &rtOpts->statusLog.logEnabled, rtOpts->statusLog.logEnabled, + "Enable / disable writing status information to file."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:status_update_interval", + PTPD_RESTART_LOGGING, INTTYPE_INT, &rtOpts->statusFileUpdateInterval, rtOpts->statusFileUpdateInterval, + "Status file update interval in seconds.", RANGECHECK_RANGE, + 1,30); + +#ifdef RUNTIME_DEBUG + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "global:debug_level", + PTPD_RESTART_NONE, (uint8_t*)&rtOpts->debug_level, rtOpts->debug_level, + "Specify debug level (if compiled with RUNTIME_DEBUG).", + "LOG_INFO", LOG_INFO, + "LOG_DEBUG", LOG_DEBUG, + "LOG_DEBUG1", LOG_DEBUG1, + "LOG_DEBUG2", LOG_DEBUG2, + "LOG_DEBUG3", LOG_DEBUG3, + "LOG_DEBUGV", LOG_DEBUGV, NULL + ); +#else + if (!(opCode & CFGOP_PARSE_QUIET) && CONFIG_ISSET("global:debug_level")) + INFO("Runtime debug not enabled. Please compile with RUNTIME_DEBUG to use global:debug_level.\n"); +#endif /* RUNTIME_DEBUG */ + + /* + * Log files are reopened and / or re-created on SIGHUP anyway, + */ + + /* if log file specified, enable file logging - otherwise disable */ + CONFIG_KEY_TRIGGER("global:log_file", rtOpts->eventLog.logEnabled,TRUE,FALSE); + parseResult &= configMapString(opCode, opArg, dict, target, "global:log_file", + PTPD_RESTART_LOGGING, rtOpts->eventLog.logPath, sizeof(rtOpts->eventLog.logPath), rtOpts->eventLog.logPath, + "Specify log file path (event log). Setting this enables logging to file."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:log_file_max_size", + PTPD_RESTART_LOGGING, INTTYPE_U32, &rtOpts->eventLog.maxSize, rtOpts->eventLog.maxSize, + "Maximum log file size (in kB) - log file will be truncated if size exceeds\n" + " the limit. 0 - no limit.", RANGECHECK_MIN,0,0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:log_file_max_files", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->eventLog.maxFiles, rtOpts->eventLog.maxFiles, + "Enable log rotation of the sync packet record file up to n files.\n" + " 0 - do not rotate.\n", RANGECHECK_RANGE,0, 100); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:log_file_truncate", + PTPD_RESTART_LOGGING, &rtOpts->eventLog.truncateOnReopen, rtOpts->eventLog.truncateOnReopen, + "Truncate the log file every time it is (re) opened: startup and SIGHUP."); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "global:log_level", + PTPD_RESTART_NONE, &rtOpts->logLevel, rtOpts->logLevel, + "Specify log level (only messages at this priority or higer will be logged).\n" + " The minimal level is LOG_ERR. LOG_ALL enables debug output if compiled with\n" + " RUNTIME_DEBUG.", + "LOG_ERR", LOG_ERR, + "LOG_WARNING", LOG_WARNING, + "LOG_NOTICE", LOG_NOTICE, + "LOG_INFO", LOG_INFO, + "LOG_ALL", LOG_ALL, NULL + ); + + /* if statistics file specified, enable statistics logging - otherwise disable - log_statistics also controlled further below*/ + CONFIG_KEY_TRIGGER("global:statistics_file", rtOpts->statisticsLog.logEnabled,TRUE,FALSE); + CONFIG_KEY_TRIGGER("global:statistics_file", rtOpts->logStatistics,TRUE,FALSE); + parseResult &= configMapString(opCode, opArg, dict, target, "global:statistics_file", + PTPD_RESTART_LOGGING, rtOpts->statisticsLog.logPath, sizeof(rtOpts->statisticsLog.logPath), rtOpts->statisticsLog.logPath, + "Specify statistics log file path. Setting this enables logging of \n" + " statistics, but can be overriden with global:log_statistics."); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:statistics_log_interval", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->statisticsLogInterval, rtOpts->statisticsLogInterval, + "Log timing statistics every n seconds for Sync and Delay messages\n" + " (0 - log all).",RANGECHECK_MIN,0,0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:statistics_file_max_size", + PTPD_RESTART_LOGGING, INTTYPE_U32, &rtOpts->statisticsLog.maxSize, rtOpts->statisticsLog.maxSize, + "Maximum statistics log file size (in kB) - log file will be truncated\n" + " if size exceeds the limit. 0 - no limit.",RANGECHECK_MIN,0,0); + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:statistics_file_max_files", + PTPD_RESTART_LOGGING, INTTYPE_INT, &rtOpts->statisticsLog.maxFiles, rtOpts->statisticsLog.maxFiles, + "Enable log rotation of the statistics file up to n files. 0 - do not rotate.", RANGECHECK_RANGE,0, 100); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:statistics_file_truncate", + PTPD_RESTART_LOGGING, &rtOpts->statisticsLog.truncateOnReopen, rtOpts->statisticsLog.truncateOnReopen, + "Truncate the statistics file every time it is (re) opened: startup and SIGHUP."); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:dump_packets", + PTPD_RESTART_NONE, &rtOpts->displayPackets, rtOpts->displayPackets, + "Dump the contents of every PTP packet"); + + /* this also checks if the verbose_foreground flag is set correctly */ + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:verbose_foreground", + PTPD_RESTART_DAEMON, &rtOpts->nonDaemon, rtOpts->nonDaemon, + "Run in foreground with statistics and all messages logged to stdout.\n" + " Overrides log file and statistics file settings and disables syslog.\n"); + + if(CONFIG_ISTRUE("global:verbose_foreground")) { + rtOpts->useSysLog = FALSE; + rtOpts->logStatistics = TRUE; + rtOpts->statisticsLogInterval = 0; + rtOpts->eventLog.logEnabled = FALSE; + rtOpts->statisticsLog.logEnabled = FALSE; + } + + /* + * this HAS to be executed after the verbose_foreground mapping because of the same + * default field used for both. verbose_foreground triggers nonDaemon which is OK, + * but we don't want foreground=y to switch on verbose_foreground if not set. + */ + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:foreground", + PTPD_RESTART_DAEMON, &rtOpts->nonDaemon, rtOpts->nonDaemon, + "Run in foreground - ignored when global:verbose_foreground is set"); + + if(CONFIG_ISTRUE("global:verbose_foreground")) { + rtOpts->nonDaemon = TRUE; + } + + /* If this is processed after verbose_foreground, we can still control logStatistics */ + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:log_statistics", + PTPD_RESTART_NONE, &rtOpts->logStatistics, rtOpts->logStatistics, + "Log timing statistics for every PTP packet received\n"); + + parseResult &= configMapSelectValue(opCode, opArg, dict, target, "global:statistics_timestamp_format", + PTPD_RESTART_NONE, &rtOpts->statisticsTimestamp, rtOpts->statisticsTimestamp, + "Timestamp format used when logging timing statistics\n" + " (when global:log_statistics is enabled):\n" + " datetime - formatttted date and time: YYYY-MM-DD hh:mm:ss.uuuuuu\n" + " unix - Unix timestamp with nanoseconds: s.ns\n" + " both - Formatted date and time, followed by unix timestamp\n" + " (adds one extra field to the log)\n", + "datetime", TIMESTAMP_DATETIME, + "unix", TIMESTAMP_UNIX, + "both", TIMESTAMP_BOTH, NULL + ); + + /* If statistics file is enabled but logStatistics isn't, disable logging to file */ + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->statisticsLog.logEnabled && !rtOpts->logStatistics, + rtOpts->statisticsLog.logEnabled, FALSE, rtOpts->statisticsLog.logEnabled); + +#if (defined(linux) && defined(HAVE_SCHED_H)) || defined(HAVE_SYS_CPUSET_H) || defined (__QNXNTO__) + parseResult &= configMapInt(opCode, opArg, dict, target, "global:cpuaffinity_cpucore", PTPD_CHANGE_CPUAFFINITY, INTTYPE_INT, &rtOpts->cpuNumber, rtOpts->cpuNumber, + "Bind "PTPD_PROGNAME" process to a selected CPU core number.\n" + " 0 = first CPU core, etc. -1 = do not bind to a single core.", RANGECHECK_RANGE, + -1,255); +#endif /* (linux && HAVE_SCHED_H) || HAVE_SYS_CPUSET_H */ + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:statistics_update_interval", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->statsUpdateInterval, + rtOpts->statsUpdateInterval, + "Clock synchronisation statistics update interval in seconds\n", RANGECHECK_RANGE,1, 60); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "global:periodic_updates", + PTPD_RESTART_LOGGING, &rtOpts->periodicUpdates, rtOpts->periodicUpdates, + "Log a status update every time statistics are updated (global:statistics_update_interval).\n" + " The updates are logged even when ptpd is configured without statistics support"); + +#ifdef PTPD_STATISTICS + + CONFIG_CONDITIONAL_ASSERTION( rtOpts->servoStabilityDetection && ( + (rtOpts->statsUpdateInterval * rtOpts->servoStabilityPeriod) / 60 >= + rtOpts->servoStabilityTimeout), + "The configured servo stabilisation timeout has to be longer than\n" + " servo stabilisation period"); + +#endif /* PTPD_STATISTICS */ + + parseResult &= configMapInt(opCode, opArg, dict, target, "global:timingdomain_election_delay", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->electionDelay, rtOpts->electionDelay, + " Delay (seconds) before releasing a time service (NTP or PTP)" + " and electing a new one to control a clock. 0 = elect immediately\n", RANGECHECK_RANGE,0, 3600); + + +/* ===== ntpengine section ===== */ + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ntpengine:enabled", + PTPD_RESTART_NTPENGINE, &rtOpts->ntpOptions.enableEngine, rtOpts->ntpOptions.enableEngine, + "Enable NTPd integration"); + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "ntpengine:control_enabled", + PTPD_RESTART_NONE, &rtOpts->ntpOptions.enableControl, rtOpts->ntpOptions.enableControl, + "Enable control over local NTPd daemon"); + + CONFIG_KEY_DEPENDENCY("ntpengine:control_enabled", "ntpengine:enabled"); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ntpengine:check_interval", + PTPD_RESTART_NTPCONFIG, INTTYPE_INT, &rtOpts->ntpOptions.checkInterval, + rtOpts->ntpOptions.checkInterval, + "NTP control check interval in seconds\n", RANGECHECK_RANGE, 5, 600); + + parseResult &= configMapInt(opCode, opArg, dict, target, "ntpengine:key_id", + PTPD_RESTART_NONE, INTTYPE_INT, &rtOpts->ntpOptions.keyId, rtOpts->ntpOptions.keyId, + "NTP key number - must be configured as a trusted control key in ntp.conf,\n" + " and be non-zero for the ntpengine:control_enabled setting to take effect.\n", RANGECHECK_RANGE,0, 65535); + + parseResult &= configMapString(opCode, opArg, dict, target, "ntpengine:key", + PTPD_RESTART_NONE, rtOpts->ntpOptions.key, sizeof(rtOpts->ntpOptions.key), rtOpts->ntpOptions.key, + "NTP key (plain text, max. 20 characters) - must match the key configured in\n" + " ntpd's keys file, and must be non-zero for the ntpengine:control_enabled\n" + " setting to take effect.\n"); + + CONFIG_KEY_DEPENDENCY("ntpengine:control:enabled", "ntpengine:key_id"); + CONFIG_KEY_DEPENDENCY("ntpengine:control:enabled", "ntpengine:key"); + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + + parseResult &= configMapBoolean(opCode, opArg, dict, target, "score:autosar", + PTPD_RESTART_PROTOCOL, &rtOpts->scoreConfig.autosar, rtOpts->scoreConfig.autosar, + "Enable AUTOSAR support:\n" + " Deactivate ANNOUNCE messages.\n" + " Use a manually configured time master.\n"); + + // Condition, Dep, messageText + CONFIG_KEY_CONDITIONAL_WARNING_ISSET((rtOpts->disableBMCA == FALSE) && rtOpts->scoreConfig.autosar, + "score:autosar", + "AUTOSAR profile specifics can only be used with disabled BMCA.\n"); + + // Condition, Variable, Value, Otherwise + CONFIG_KEY_CONDITIONAL_TRIGGER(rtOpts->disableBMCA == FALSE, rtOpts->scoreConfig.autosar, FALSE, rtOpts->scoreConfig.autosar); + + + parseResult &= configMapDouble(opCode, opArg, dict, target, "score:globaltimepropagationdelay", PTPD_UPDATE_DATASETS, + &rtOpts->scoreConfig.GlobalTimePropagationDelay, rtOpts->scoreConfig.GlobalTimePropagationDelay, + "If cyclic propagation delay measurement is disabled, this parameter replaces a measured propagation delay by a fixed value. Unit: seconds\n", + RANGECHECK_RANGE, 0.0, 4.0); + + // Condition, Dep, messageText + CONFIG_KEY_CONDITIONAL_WARNING_ISSET((rtOpts->delayMechanism != DELAY_DISABLED) || (rtOpts->scoreConfig.autosar == FALSE), + "score:globaltimepropagationdelay", + "GlobalTimePropagationDelay can only be used together with ptpengine:delay_mechanism=DELAY_DISABLED and enabled AUTOSAR profile specifics.\n"); +#endif + // ##### SCORE MODIFICATION END ##### + +/* ============== END CONFIG MAPPINGS, TRIGGERS AND DEPENDENCIES =========== */ + +/* ==== Any additional logic should go here ===== */ + + rtOpts->ifaceName = rtOpts->primaryIfaceName; + + /* Check timing packet ACLs */ + if(rtOpts->timingAclEnabled) { + + int pResult, dResult; + + if((pResult = maskParser(rtOpts->timingAclPermitText, NULL)) == -1) + ERROR("Error while parsing timing permit access list: \"%s\"\n", + rtOpts->timingAclPermitText); + if((dResult = maskParser(rtOpts->timingAclDenyText, NULL)) == -1) + ERROR("Error while parsing timing deny access list: \"%s\"\n", + rtOpts->timingAclDenyText); + + /* -1 = ACL format error*/ + if(pResult == -1 || dResult == -1) { + parseResult = FALSE; + rtOpts->timingAclEnabled = FALSE; + } + /* 0 = no entries - we simply don't match */ + if(pResult == 0 && dResult == 0) { + rtOpts->timingAclEnabled = FALSE; + } + } + + + /* Check management message ACLs */ + if(rtOpts->managementAclEnabled) { + + int pResult, dResult; + + if((pResult = maskParser(rtOpts->managementAclPermitText, NULL)) == -1) + ERROR("Error while parsing management permit access list: \"%s\"\n", + rtOpts->managementAclPermitText); + if((dResult = maskParser(rtOpts->managementAclDenyText, NULL)) == -1) + ERROR("Error while parsing management deny access list: \"%s\"\n", + rtOpts->managementAclDenyText); + + /* -1 = ACL format error*/ + if(pResult == -1 || dResult == -1) { + parseResult = FALSE; + rtOpts->managementAclEnabled = FALSE; + } + /* 0 = no entries - we simply don't match */ + if(pResult == 0 && dResult == 0) { + rtOpts->managementAclEnabled = FALSE; + } + } + + /* Scale the maxPPM to PPB */ + rtOpts->servoMaxPpb *= 1000; + + /* Shift DSCP to accept the 6-bit value */ + rtOpts->dscpValue = rtOpts->dscpValue << 2; + + /* + * We're in hybrid mode and we haven't specified the delay request interval: + * use override with a default value + */ + if((rtOpts->ipMode == IPMODE_HYBRID) && + !CONFIG_ISSET("ptpengine:log_delayreq_interval")) + rtOpts->ignore_delayreq_interval_master=TRUE; + + /* + * We're in unicast slave-capable mode and we haven't specified the delay request interval: + * use override with a default value + */ + if((rtOpts->ipMode == IPMODE_UNICAST && + rtOpts->clockQuality.clockClass > 127) && + !CONFIG_ISSET("ptpengine:log_delayreq_interval")) + rtOpts->ignore_delayreq_interval_master=TRUE; + + /* + * construct the lock file name based on operation mode: + * if clock class is <128 (master only), use "master" and interface name + * if clock class is >127 (can be slave), use clock driver and interface name + */ + if(rtOpts->autoLockFile) { + + memset(rtOpts->lockFile, 0, PATH_MAX); + snprintf(rtOpts->lockFile, PATH_MAX, + "%s/"PTPD_PROGNAME"_%s_%s.lock", + rtOpts->lockDirectory, + (rtOpts->clockQuality.clockClass<128 && !rtOpts->slaveOnly) ? "master" : DEFAULT_CLOCKDRIVER, + rtOpts->ifaceName); + DBG("Automatic lock file name is: %s\n", rtOpts->lockFile); + /* + * Otherwise use default lock file name, with the specified lock directory + * which will be set do default from constants_dep.h if not configured + */ + } else { + if(!CONFIG_ISSET("global:lock_file")) + snprintf(rtOpts->lockFile, PATH_MAX, + "%s/%s", rtOpts->lockDirectory, DEFAULT_LOCKFILE_NAME); + } + +/* ==== END additional logic */ + + if(opCode & CFGOP_PARSE) { + + findUnknownSettings(opCode, dict, target); + + if (parseResult) + INFO("Configuration OK\n"); + else + ERROR("There are errors in the configuration - see previous messages\n"); + } + + if ((opCode & CFGOP_PARSE || opCode & CFGOP_PARSE_QUIET) && parseResult) { + return target; + } + + dictionary_del(&target); + return NULL; + + +} + +/** + * Wrapper around iniparser_load, which loads the ini file + * contents into an existing dictionary - iniparser_load + * creates a new dictionary + */ +Boolean +loadConfigFile(dictionary **target, RunTimeOpts *rtOpts) +{ + + dictionary *dict; + + if ( (dict = iniparser_load(rtOpts->configFile)) == NULL) { + ERROR("Could not load configuration file: %s\n", rtOpts->configFile); + return FALSE; + } + + dictionary_merge( dict, *target, 0, 0, NULL); + dictionary_del(&dict); + + return TRUE; +} + + +/* - + * this function scans argv looking for what looks like correctly formatted + * long options in the form "--section:key=value", "-section:key=value", + * "--section:key value" and "-section:key value". + * it then clears them from argv so that getopt can deal with the rest. + * This is in a way better than getopt_long() because we don't need to + * pre-define all possible options. Unknown or malformed ones are ignored. + */ +void +loadCommandLineKeys(dictionary* dict, int argc,char** argv) +{ + + int i; + char key[PATH_MAX],val[PATH_MAX]; + + for ( i=0; i 3 && + // ##### SCORE MODIFICATION BEGIN ##### + /* qnx8_introduction - the index function is deprecated from qnx8(originally defined in string.h) + strchr is used instead */ + strchr(argv[i],':') != NULL ) { + // ##### SCORE MODIFICATION END ##### + /* check if the option is passed as sec:key=value */ + if (sscanf(argv[i],"--%[-_a-zA-Z0-9:]=%s",key,val)==2 || + sscanf(argv[i],"-%[-_a-zA-Z0-9:]=%s",key,val)==2) { + /* wipe the used argv entry so that getopt doesn't get confused */ + argv[i]=""; + } else + /* + * there are no optional arguments for key:sec options - if there's no =, + * the next argument is the value. + */ + if (sscanf(argv[i],"--%[-_a-zA-Z0-9:]",key)==1 || + sscanf(argv[i],"-%[-_a-zA-Z0-9:]",key)==1 ) { + argv[i]=""; + memset(val, 0, PATH_MAX); + if (i+1 < argc) { + if( (argv[i+1][0]!='-') && + ( (strlen(argv[i+i]) > 1) && argv[i+1][1] !='-' )) { + strncpy(val,argv[i+1],PATH_MAX); + argv[i+1]=""; + } + + } + + } + /* + * there is no argument --sec:key=val + */ + else { + val[0] = '\0'; + key[0] = '\0'; + } + + if(strlen(key) > 0) { + DBGV("setting loaded from command line: %s = %s\n",key,val); + dictionary_set(dict, key, val); + } + + } + + } +} + +/** + * Create a dummy rtOpts with defaults, create a dummy dictionary, + * Set the "secret" key in the dictionary, causing parseConfig + * to switch from parse mode to print default mode + */ +void +printDefaultConfig() +{ + + RunTimeOpts rtOpts; + dictionary *dict; + + loadDefaultSettings(&rtOpts); + dict = dictionary_new(0); + + printf( "; ========================================\n"); + printf( "; "USER_DESCRIPTION" version "USER_VERSION" default configuration\n"); + printf( "; ========================================\n\n"); + printf( "; NOTE: the following settings are affected by ptpengine:preset selection:\n" + "; ptpengine:slave_only\n" + "; clock:no_adjust\n" + "; ptpengine:clock_class - allowed range and default value\n" + "; To see all preset settings, run "PTPD_PROGNAME" -H (--long-help)\n"); + + /* NULL will always be returned in this mode */ + parseConfig(CFGOP_PRINT_DEFAULT | CFGOP_PARSE_QUIET, NULL, dict, &rtOpts); + dictionary_del(&dict); + + printf("\n; ========= newline required in the end ==========\n\n"); + +} + + + +/** + * Create a dummy rtOpts with defaults, create a dummy dictionary, + * Set the "secret" key in the dictionary, causing parseConfig + * to switch from parse mode to help mode. + */ +void +printConfigHelp() +{ + + RunTimeOpts rtOpts; + dictionary *dict; + + loadDefaultSettings(&rtOpts); + dict = dictionary_new(0); + + printf("\n============== Full list of "PTPD_PROGNAME" settings ========\n\n"); + + /* NULL will always be returned in this mode */ + parseConfig(CFGOP_HELP_FULL | CFGOP_PARSE_QUIET, NULL, dict, &rtOpts); + + dictionary_del(&dict); + +} + +/** + * Create a dummy rtOpts with defaults, create a dummy dictionary, + * Set the "secret" key in the dictionary, causing parseConfig + * to switch from parse mode to help mode, for a selected key only. + */ +void +printSettingHelp(char* key) +{ + + RunTimeOpts rtOpts; + dictionary *dict; + char* origKey = strdup(key); + + loadDefaultSettings(&rtOpts); + dict = dictionary_new(0); + + + printf("\n"); + /* NULL will always be returned in this mode */ + parseConfig(CFGOP_HELP_SINGLE | CFGOP_PARSE_QUIET, key, dict, &rtOpts); + + /* if the setting has been found (and help printed), the first byte will be cleared */ + if(key[0] != '\0') { + printf("Unknown setting: %s\n\n", origKey); + } + printf("Use -H or --long-help to show help for all settings.\n\n"); + + dictionary_del(&dict); + free(origKey); +} + +/** + * Handle standard options with getopt_long. Returns FALSE if ptpd should not continue. + * If a required setting, such as interface name, or a setting + * requiring a range check is to be set via getopts_long, + * the respective currentConfig dictionary entry should be set, + * instead of just setting the rtOpts field. + */ +Boolean loadCommandLineOptions(RunTimeOpts* rtOpts, dictionary* dict, int argc, char** argv, Integer16* ret) { + + int c; +#ifdef HAVE_GETOPT_LONG + int opt_index = 0; +#endif /* HAVE_GETOPT_LONG */ + *ret = 0; + + /* there's NOTHING wrong with this */ + if(argc==1) { + *ret = 1; + goto short_help; + } + +#ifdef HAVE_GETOPT_LONG + /** + * A minimal set of long and short CLI options, + * for basic operations only. Maintained compatibility + * with previous versions. Any other settings + * should be given in the config file or using + * --section:key options + **/ + static struct option long_options[] = { + {"config-file", required_argument, 0, 'c'}, + {"check-config", optional_argument, 0, 'k'}, + {"interface", required_argument, 0, 'i'}, + {"domain", required_argument, 0, 'd'}, + {"slaveonly", no_argument, 0, 's'}, + {"masterslave", no_argument, 0, 'm'}, + {"masteronly", no_argument, 0, 'M'}, + {"hybrid", no_argument, 0, 'y'}, + {"e2e", optional_argument, 0, 'E'}, + {"p2p", optional_argument, 0, 'P'}, + {"noadjust", optional_argument, 0, 'n'}, + {"ignore-lock", optional_argument, 0, 'L'}, + {"auto-lock", optional_argument, 0, 'A'}, + {"lockfile", optional_argument, 0, 'l'}, + {"print-lockfile", optional_argument, 0, 'p'}, + {"lock-directory", required_argument, 0, 'R'}, + {"log-file", required_argument, 0, 'f'}, + {"statistics-file", required_argument, 0, 'S'}, + {"delay-interval", required_argument, 0, 'r'}, + {"delay-override", no_argument, 0, 'a'}, + {"debug", no_argument, 0, 'D'}, + {"version", optional_argument, 0, 'v'}, + {"foreground", no_argument, 0, 'C'}, + {"verbose", no_argument, 0, 'V'}, + {"help", optional_argument, 0, 'h'}, + {"long-help", no_argument, 0, 'H'}, + {"explain", required_argument, 0, 'e'}, + {"default-config", optional_argument, 0, 'O'}, + {"templates", required_argument, 0, 't'}, + {"show-templates", no_argument, 0, 'T'}, + {"unicast", optional_argument, 0, 'U'}, + {"unicast-negotiation", optional_argument, 0, 'g'}, + {"unicast-destinations", required_argument, 0, 'u'}, + {0, 0 , 0, 0} + }; + + while ((c = getopt_long(argc, argv, "?c:kb:i:d:sgmGMWyUu:nf:S:r:DvCVHTt:he:Y:tOLEPAaR:l:p", long_options, &opt_index)) != -1) { +#else + while ((c = getopt(argc, argv, "?c:kb:i:d:sgmGMWyUu:nf:S:r:DvCVHTt:he:Y:tOLEPAaR:l:p")) != -1) { +#endif + switch(c) { +/* non-config options first */ + + /* getopt error or an actual ? */ + case '?': + printf("Run "PTPD_PROGNAME" with -h to see available command-line options.\n"); + printf("Run "PTPD_PROGNAME" with -H or --long-help to show detailed help for all settings.\n\n"); + *ret = 1; + return FALSE; + case 'h': +short_help: + printf("\n"); + printShortHelp(); + return FALSE; + case 'H': + printLongHelp(); + return FALSE; + case 'e': + if(strlen(optarg) > 0) + printSettingHelp(optarg); + return FALSE; + case 'O': + printDefaultConfig(); + return FALSE; + case 'T': + dumpConfigTemplates(); + return FALSE; +/* regular ptpd options */ + + /* config file path */ + case 'c': + strncpy(rtOpts->configFile, optarg, PATH_MAX); + break; + /* check configuration and exit */ + case 'k': + rtOpts->checkConfigOnly = TRUE; + break; + /* interface */ + case 'b': + WARN_DEPRECATED('b', 'i', "interface", "ptpengine:interface"); + case 'i': + /* if we got a number here, we've been given the domain number */ + if( (c=='i') && strlen(optarg) > 0 && isdigit((unsigned char)optarg[0]) ) { + WARN_DEPRECATED_COMMENT('i', 'd', "domain", "ptpengine:domain", + "for specifying domain number "); + dictionary_set(dict,"ptpengine:domain", optarg); + } else + dictionary_set(dict,"ptpengine:interface", optarg); + break; + /* domain */ + case 'd': + dictionary_set(dict,"ptpengine:domain", optarg); + break; + case 's': + dictionary_set(dict,"ptpengine:preset", "slaveonly"); + break; + /* master/slave */ + case 'W': + WARN_DEPRECATED('W', 'm', "masterslave", "ptpengine:preset=masterslave"); + case 'm': + dictionary_set(dict,"ptpengine:preset", "masterslave"); + break; + /* master only */ + case 'G': + WARN_DEPRECATED('G', 'M', "masteronly", "ptpengine:preset=masteronly"); + case 'M': + dictionary_set(dict,"ptpengine:preset", "masteronly"); + break; + case 'y': + dictionary_set(dict,"ptpengine:ip_mode", "hybrid"); + break; + /* unicast */ + case 'U': + dictionary_set(dict,"ptpengine:ip_mode", "unicast"); + break; + case 'g': + dictionary_set(dict,"ptpengine:unicast_negotiation", "y"); + break; + case 'u': + dictionary_set(dict,"ptpengine:ip_mode", "unicast"); + dictionary_set(dict,"ptpengine:unicast_destinations", optarg); + break; + case 'n': + dictionary_set(dict,"clock:no_adjust", "Y"); + break; + /* log file */ + case 'f': + dictionary_set(dict,"global:log_file", optarg); + break; + /* statistics file */ + case 'S': + dictionary_set(dict,"global:statistics_file", optarg); + break; + case 't': + dictionary_set(dict,"global:config_templates", optarg); + /* Override delay request interval from master */ + case 'a': + dictionary_set(dict,"ptpengine:log_delayreq_override", "Y"); + break; + /* Delay request interval - needed for hybrid mode */ + case 'Y': + WARN_DEPRECATED('Y', 'r', "delay-interval", "ptpengine:log_delayreq_interval"); + case 'r': + dictionary_set(dict,"ptpengine:log_delayreq_interval", optarg); + break; + case 'D': +#ifdef RUNTIME_DEBUG + (rtOpts->debug_level)++; + if(rtOpts->debug_level > LOG_DEBUGV ){ + rtOpts->debug_level = LOG_DEBUGV; + } +#else + printf("Runtime debug not enabled. Please compile with RUNTIME_DEBUG\n"); +#endif + break; + /* print version string */ + case 'v': + printf(PTPD_PROGNAME" version "USER_VERSION +#ifdef CODE_REVISION + CODE_REVISION + " built on "BUILD_DATE +#endif + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + " with SCORE modifications" +#endif + // ##### SCORE MODIFICATION END ##### + "\n"); + + return FALSE; + /* run in foreground */ + case 'C': + rtOpts->nonDaemon = TRUE; + dictionary_set(dict,"global:foreground", "Y"); + break; + /* verbose mode */ + case 'V': + rtOpts->nonDaemon = TRUE; + dictionary_set(dict,"global:foreground", "Y"); + dictionary_set(dict,"global:verbose_foreground", "Y"); + break; + /* Skip locking */ + case 'L': + dictionary_set(dict,"global:ignore_lock", "Y"); + break; + /* End to end delay detection mode */ + case 'E': + dictionary_set(dict,"ptpengine:delay_mechanism", "E2E"); + break; + /* Peer to peer delay detection mode */ + case 'P': + dictionary_set(dict,"ptpengine:delay_mechanism", "P2P"); + break; + /* Auto-lock */ + case 'A': + dictionary_set(dict,"global:auto_lock", "Y"); + break; + /* Print lock file only */ + case 'p': + rtOpts->printLockFile = TRUE; + break; + /* Lock file */ + case 'l': + dictionary_set(dict,"global:lock_file", optarg); + break; + /* Lock directory */ + case 'R': + dictionary_set(dict,"global:lock_directory", optarg); + break; + default: + break; + + } + } + +return TRUE; + +} + +/* Display informatin about the built-in presets */ +void +printPresetHelp() +{ + + int i = 0; + PtpEnginePreset preset; + RunTimeOpts defaultOpts; + + loadDefaultSettings(&defaultOpts); + + printf("\n==================AVAILABLE PTP ENGINE PRESETS===============\n\n"); + + for(i = PTP_PRESET_NONE; i < PTP_PRESET_MAX; i++) { + + preset = getPtpPreset(i, &defaultOpts); + printf(" preset name: %s\n", preset.presetName); + printf(" slave only: %s\n", preset.slaveOnly ? "TRUE" : "FALSE"); + printf("allow clock control: %s\n", preset.noAdjust ? "FALSE" : "TRUE"); + printf("default clock class: %d%s\n", preset.clockClass.defaultValue, + preset.clockClass.minValue == preset.clockClass.maxValue ? + " (only allowed value)" : ""); + if (preset.clockClass.minValue != preset.clockClass.maxValue) { + + printf(" clock class range: %d..%d\n", + preset.clockClass.minValue, + preset.clockClass.maxValue); + } + printf("\n"); + + } + + printf("\n=============================================================\n"); + +} + +/* print "short" help - standard parameters only */ +void +printShortHelp() +{ + printf( + USER_DESCRIPTION" "USER_VERSION"\n" + "\n" + "usage: "PTPD_PROGNAME" < --section:key=value...>\n" + "\n" + "WARNING: Any command-line options take priority over options from config file!\n" +#ifndef HAVE_GETOPT_LONG + "WARNING: *** long options below (--some-option) are not supported on this system! \n" + "NOTE: *** config file style options (--section:key=value) are still supported\n" +#endif + "\n" + "Basic options: \n" + "\n" + "-c --config-file [path] Configuration file\n" + "-k --check-config Check configuration and exit\n" + "-v --version Print version string\n" + "-h --help Show this help screen\n" + "-H --long-help Show detailed help for all settings and behaviours\n" + "-e --explain [section:key] Show help for a single setting\n" + "-O --default-config Output default configuration (usable as config file)\n" + "-L --ignore-lock Skip lock file checks and locking\n" + "-A --auto-lock Use preset / port mode specific lock files\n" + "-l --lockfile [path] Specify path to lock file\n" + "-p --print-lockfile Print path to lock file and exit (useful for init scripts)\n" + "-R --lock-directory [path] Directory to store lock files\n" + "-f --log-file [path] global:log_file=[path] Log file\n" + "-S --statistics-file [path] global:statistics_file=[path] Statistics file\n" + "-T --show-templates display available configuration templates\n" + "-t --templates [name],[name],[...]\n" + " apply configuration template(s) - see man(5) ptpd2.conf\n" + "\n" + "Basic PTP protocol and daemon configuration options: \n" + "\n" + "Command-line option Config key(s) Description\n" + "------------------------------------------------------------------------------------\n" + + "-i --interface [dev] ptpengine:interface= Interface to use (required)\n" + "-d --domain [n] ptpengine:domain= PTP domain number\n" + "-s --slaveonly ptpengine:preset=slaveonly Slave only mode\n" + "-m --masterslave ptpengine:preset=masterslave Master, slave when not best GM\n" + "-M --masteronly ptpengine:preset=masteronly Master, passive when not best GM\n" + "-y --hybrid ptpengine:ip_mode=hybrid Hybrid mode (multicast for sync\n" + " and announce, unicast for delay\n" + " request and response)\n" + "-U --unicast ptpengine:ip_mode=unicast Unicast mode\n" + "-g --unicast-negotiation ptpengine:unicast_negotiation=y Enable unicast negotiation (signaling)\n" + "-u --unicast-destinations ptpengine:ip_mode=unicast Unicast destination list\n" + " [ip/host, ...] ptpengine:unicast_destinations=\n\n" + "-E --e2e ptpengine:delay_mechanism=E2E End to end delay detection\n" + "-P --p2p ptpengine:delay_mechanism=P2P Peer to peer delay detection\n" + "\n" + "-a --delay-override ptpengine:log_delayreq_override Override delay request interval\n" + " announced by master\n" + "-r --delay-interval [n] ptpengine:log_delayreq_interval= Delay request interval\n" + " (log 2)\n" + "\n" + "-n --noadjust clock:no_adjust Do not adjust the clock\n" + "-D --debug global:debug_level= Debug level\n" + "-C --foreground global:foreground= Don't run in background\n" + "-V --verbose global:verbose_foreground= Run in foreground, log all\n" + " messages to standard output\n" + "\n" + "------------------------------------------------------------------------------------\n" + "\n" + PTPD_PROGNAME" accepts a configuration file (.ini style) where settings are either\n" + "grouped into sections:\n\n" + "[section]\n" + "; comment\n" + "key = value\n\n" + "or are specified as:\n\n" + "section:key = value\n\n" + "All settings can also be supplied as command-line parameters (after other options):\n" + "\n" + "--section:key= --section:key -section:key= -section:key \n" + "\n" + "To see the full help for all configurable options, run: "PTPD_PROGNAME" with -H or --long-help\n" + "To see help for a single setting only, run: "PTPD_PROGNAME" with -e (--explain) [section:key]\n" + "To see all default settings with descriptions, run: "PTPD_PROGNAME" with -O (--default-config)\n" + "\n" + "------------------------------------------------------------------------------------\n" + "\n" + USER_DESCRIPTION" compatibility options for migration from versions below 2.3.0:\n" + "\n" + "-b [dev] Network interface to use\n" + "-i [n] PTP domain number\n" + "-G 'Master mode with NTP' (ptpengine:preset=masteronly)\n" + "-W 'Master mode without NTP' (ptpengine:preset=masterslave) \n" + "-Y [n] Delay request interval (log 2)\n" + "-t Do not adjust the clock\n" + "\n" + "Note 1: the above parameters are deprecated and their use will issue a warning.\n" + "Note 2: -U and -g options have been re-purposed in 2.3.1.\n" + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + "\n" + "------------------------------------------------------------------------------------\n" + "\n" + USER_DESCRIPTION" Eclipse S-CORE specific compatibility options for AUTOSAR:\n" + "\n" + "--score:autosar= Enable AUTOSAR support (deactivate ANNOUNCE messages,...)\n" + "--score:globaltimepropagationdelay= Static path propagation delay [sec]\n" + "\n" + "Note 1: the above parameters require the '--ptpengine:disable_bmca=y' option as well.\n" + "Note 2: for compatibility with AUTOSAR also '--ptpengine:transport=ethernet' is required.\n" +#endif + // ##### SCORE MODIFICATION END ##### + "\n\n" + ); +} + +/* Print the full help */ +void +printLongHelp() +{ + + printShortHelp(); + + printConfigHelp(); + + printPresetHelp(); + + printf("\n" + " Configuration templates available (see man(5) ptpd2.conf):\n" + "\n usage:\n" + " -t [name],[name],...\n" + " --templates [name],[name],...\n" + " --global:config_templates=[name],[name],...\n\n"); + + dumpConfigTemplates(); + + printf("========================\n"); + + + printf("\n" + "Possible internal states:\n" + " init: INITIALIZING\n" + " flt: FAULTY\n" + " lstn_init: LISTENING (first time)\n" + " lstn_reset: LISTENING (non first time)\n" + " pass: PASSIVE Master (not best in BMC, not announcing)\n" + " uncl: UNCALIBRATED\n" + " slv: SLAVE\n" + " pmst: PRE Master\n" + " mst: ACTIVE Master\n" + " dsbl: DISABLED\n" + " ?: UNKNOWN state\n" + "\n" + + "Handled signals:\n" + " SIGHUP Reload configuration file and close / re-open log files\n" + " SIGUSR1 Manually step clock to current OFM value\n" + " (overides clock:no_reset, but honors clock:no_adjust)\n" + " SIGUSR2 Dump all PTP protocol counters to current log target\n" + " (and clear if ptpengine:sigusr2_clears_counters set)\n" + "\n" + " SIGINT|SIGTERM Close open files, remove lock file and exit cleanly\n" + " SIGKILL Force an unclean exit\n" + "\n" + "BMC Algorithm defaults:\n" + " Software: P1(128) > Class(13|248) > Accuracy(\"unk\"/0xFE) > Variance(65536) > P2(128)\n" + ); + +} + + +/* + * Iterate through every key in newConfig and compare to oldConfig, + * return TRUE if both equal, otherwise FALSE; + */ +Boolean +compareConfig(dictionary* newConfig, dictionary* oldConfig) +{ + + int i = 0; + + if( newConfig == NULL || oldConfig == NULL) return -1; + + for(i = 0; i < newConfig->n; i++) { + + if(newConfig->key[i] == NULL) + continue; + + if(strcmp(newConfig->val[i], dictionary_get(oldConfig, newConfig->key[i],"")) != 0 ) { + DBG("Setting %s changed from '%s' to '%s'\n", newConfig->key[i],dictionary_get(oldConfig, newConfig->key[i],""), + newConfig->val[i]); + return FALSE; + } + } + +return TRUE; + +} + +/* Compare two configurations and set flags to mark components requiring restart */ +int checkSubsystemRestart(dictionary* newConfig, dictionary* oldConfig, RunTimeOpts *rtOpts) +{ + + int restartFlags = 0; + dictionary *tmpDict = dictionary_new(0); + + char *a, *b, *key; + + int i; + + /* both should be post-parse so will have all settings and thus same number of keys */ + for(i = 0; i < newConfig->n; i++) { + + key = newConfig->key[i]; + + if(key == NULL) { + continue; + } + + a = dictionary_get(oldConfig, key, ""); + b = dictionary_get(newConfig, key, ""); + + if(strcmp(a,b)) { + DBG("+ setting %s changed from %s to %s\n", key, a, b); + dictionary_set(tmpDict, key, "x"); + } + + } + + /* run parser in restart check mode */ + parseConfig(CFGOP_RESTART_FLAGS | CFGOP_PARSE_QUIET, &restartFlags, tmpDict, rtOpts); + + dictionary_del(&tmpDict); + + /* ========= Any additional logic goes here =========== */ + + /* Set of possible PTP port states has changed */ + if(configSettingChanged(oldConfig, newConfig, "ptpengine:slave_only") || + configSettingChanged(oldConfig, newConfig, "ptpengine:clock_class")) { + + int clockClass_old = iniparser_getint(oldConfig,"ptpengine:ptp_clockclass",0); + int clockClass_new = iniparser_getint(newConfig,"ptpengine:ptp_clockclass",0); + + /* + * We're changing from a mode where slave state is possible + * to a master only mode, or vice versa + */ + if ((clockClass_old < 128 && clockClass_new > 127) || + (clockClass_old > 127 && clockClass_new < 128)) { + + /* If we are in auto lockfile mode, trigger a check if other locks match */ + if( !configSettingChanged(oldConfig, newConfig, "global:auto_lockfile") && + DICT_ISTRUE(oldConfig, "global:auto_lockfile")) { + restartFlags|=PTPD_CHECK_LOCKS; + } + /* We can potentially be running in a different mode now, restart protocol */ + restartFlags|=PTPD_RESTART_PROTOCOL; + } + } + + /* Flag for config options that can be applied on the fly - only to trigger a log message */ + if(restartFlags == 0) + restartFlags = PTPD_RESTART_NONE; + + return restartFlags; + +} diff --git a/src/ptpd/src/dep/daemonconfig.h b/src/ptpd/src/dep/daemonconfig.h new file mode 100644 index 0000000..9ae2e66 --- /dev/null +++ b/src/ptpd/src/dep/daemonconfig.h @@ -0,0 +1,89 @@ +/** + * @file daemonconfig.h + * + * @brief definitions related to config file handling. + * + * + */ + +#ifndef PTPD_DAEMONCONFIG_H_ +#define PTPD_DAEMONCONFIG_H_ + +#include "iniparser/iniparser.h" +#include "configdefaults.h" + +/* Config reload - component restart status flags */ + +/* No restart required - can continue with new config */ +#define PTPD_RESTART_NONE 1 << 0 +/* PTP port datasetw can be updated without restarting FSM */ +#define PTPD_UPDATE_DATASETS 1 << 1 +/* PTP FSM port re-initialisation required (PTP_INITIALIZING) */ +#define PTPD_RESTART_PROTOCOL 1 << 2 +/* Network config changed: PTP_INITIALIZING handles this so far */ +#define PTPD_RESTART_NETWORK 1 << 3 +/* Logging config changes: log files need closed / reopened */ +#define PTPD_RESTART_LOGGING 1 << 4 +/* Configuration changes need checking lock files */ +#define PTPD_CHECK_LOCKS 1 << 5 +/* CPU core has changed */ +#define PTPD_CHANGE_CPUAFFINITY 1 << 6 +/* Configuration changes require daemon restart */ +#define PTPD_RESTART_DAEMON 1 << 7 + +#ifdef PTPD_STATISTICS +/* Configuration changes require filter restart */ +#define PTPD_RESTART_FILTERS 1 << 8 +#endif + +#define PTPD_RESTART_ACLS 1 << 9 +#define PTPD_RESTART_NTPENGINE 1 << 10 +#define PTPD_RESTART_NTPCONFIG 1 << 11 +#define PTPD_RESTART_ALARMS 1 << 12 + +#define LOG2_HELP "(expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.)" +#define MAX_LINE_SIZE 1024 + +/* int/double range check flags */ +enum { + RANGECHECK_NONE, + RANGECHECK_RANGE, + RANGECHECK_MIN, + RANGECHECK_MAX +}; + +/* int type to cast parsed config data to */ +enum { + INTTYPE_INT, + INTTYPE_I8, + INTTYPE_U8, + INTTYPE_I16, + INTTYPE_U16, + INTTYPE_I32, + INTTYPE_U32 +}; + +/* config parser operations */ + +#define CFGOP_PARSE 1<<0 /* parse all config options, return success/failure */ +#define CFGOP_PARSE_QUIET 1<<1 /* parse config but display no warnings */ +#define CFGOP_PRINT_DEFAULT 1<<2 /* print default settings only */ +#define CFGOP_HELP_FULL 1<<3 /* print help for all settings */ +#define CFGOP_HELP_SINGLE 1<<4 /* print help for one entry */ +#define CFGOP_RESTART_FLAGS 1<<5 /* return subsystems affected by config changes */ + +Boolean loadConfigFile (dictionary**, RunTimeOpts*); +void loadCommandLineKeys(dictionary*, int, char**); +Boolean loadCommandLineOptions(RunTimeOpts*, dictionary*, int, char** , Integer16*); +dictionary* parseConfig (int, void*, dictionary*, RunTimeOpts*); +int reloadConfig ( RunTimeOpts*, PtpClock* ); +Boolean compareConfig(dictionary* source, dictionary* target); +int checkSubsystemRestart(dictionary* newConfig, dictionary* oldConfig, RunTimeOpts *rtOpts); +void printConfigHelp(); +void printDefaultConfig(); +void printShortHelp(); +void printLongHelp(); +void printSettingHelp(char*); +void setConfig(dictionary *dict, const char* key, const char *value); + +#endif /*PTPD_DAEMONCONFIG_H_*/ diff --git a/src/ptpd/src/dep/datatypes_dep.h b/src/ptpd/src/dep/datatypes_dep.h new file mode 100644 index 0000000..77a645c --- /dev/null +++ b/src/ptpd/src/dep/datatypes_dep.h @@ -0,0 +1,179 @@ +#ifndef DATATYPES_DEP_H_ +#define DATATYPES_DEP_H_ + +#include "../ptp_primitives.h" + +/** +*\file +* \brief Implementation specific datatype + + */ + +/** +* \brief Struct used to average the offset from master +* +* The FIR filtering of the offset from master input is a simple, two-sample average + */ +typedef struct { + Integer32 nsec_prev, y; +} offset_from_master_filter; + +/** +* \brief Struct used to average the one way delay +* +* It is a variable cutoff/delay low-pass, infinite impulse response (IIR) filter. +* +* The one-way delay filter has the difference equation: s*y[n] - (s-1)*y[n-1] = x[n]/2 + x[n-1]/2, where increasing the stiffness (s) lowers the cutoff and increases the delay. + */ +typedef struct { + Integer32 nsec_prev, y; + Integer32 s_exp; +} one_way_delay_filter; + +/** +* \brief Struct containing interface information and capabilities + */ +typedef struct { + struct sockaddr afAddress; + unsigned char hwAddress[14]; + Boolean hasHwAddress; + Boolean hasAfAddress; + int addressFamily; + unsigned int flags; + int ifIndex; +} InterfaceInfo; + +/** +* \brief Struct describing network transport data + */ +typedef struct { + Integer32 eventSock, generalSock; + Integer32 multicastAddr, peerMulticastAddr; + + /* Interface address and capability descriptor */ + InterfaceInfo interfaceInfo; + + /* used by IGMP refresh */ + struct in_addr interfaceAddr; + /* Typically MAC address - outer 6 octers of ClockIdendity */ + Octet interfaceID[ETHER_ADDR_LEN]; + /* source address of last received packet - used for unicast replies to Delay Requests */ + Integer32 lastSourceAddr; + /* destination address of last received packet - used for unicast FollowUp for multiple slaves*/ + Integer32 lastDestAddr; + + uint64_t sentPackets; + uint64_t receivedPackets; + + uint64_t sentPacketsTotal; + uint64_t receivedPacketsTotal; + +#ifdef PTPD_PCAP + pcap_t *pcapEvent; + pcap_t *pcapGeneral; + Integer32 pcapEventSock; + Integer32 pcapGeneralSock; +#endif + Integer32 headerOffset; + + /* used for tracking the last TTL set */ + int ttlGeneral; + int ttlEvent; + Boolean joinedPeer; + Boolean joinedGeneral; + struct ether_addr etherDest; + struct ether_addr peerEtherDest; + Boolean txTimestampFailure; + + Ipv4AccessList* timingAcl; + Ipv4AccessList* managementAcl; + +} NetPath; + +typedef struct { + + char* logID; + char* openMode; + char logPath[PATH_MAX+1]; + FILE* logFP; + + Boolean logEnabled; + Boolean truncateOnReopen; + Boolean unlinkOnClose; + + uint32_t lastHash; + UInteger32 maxSize; + UInteger32 fileSize; + int maxFiles; + +} LogFileHandler; + + +typedef struct{ + + UInteger8 minValue; + UInteger8 maxValue; + UInteger8 defaultValue; + +} UInteger8_option; + +typedef struct{ + + Integer32 minValue; + Integer32 maxValue; + Integer32 defaultValue; + +} Integer32_option; + +typedef struct{ + + UInteger32 minValue; + UInteger32 maxValue; + UInteger32 defaultValue; + +} UInteger32_option; + +typedef struct{ + + Integer16 minValue; + Integer16 maxValue; + Integer16 defaultValue; + +} Integer16_option; + +typedef struct{ + + UInteger16 minValue; + UInteger16 maxValue; + UInteger16 defaultValue; + +} UInteger16_option; + +typedef union { uint32_t *uintval; int32_t *intval; double *doubleval; Boolean *boolval; char *strval; } ConfigPointer; +typedef union { uint32_t uintval; int32_t intval; double doubleval; Boolean boolval; char *strval; } ConfigSetting; + +typedef struct ConfigOption ConfigOption; + +struct ConfigOption { + char *key; + enum { CO_STRING, CO_INT, CO_UINT, CO_DOUBLE, CO_BOOL, CO_SELECT } type; + enum { CO_MIN, CO_MAX, CO_RANGE, CO_STRLEN } restriction; + ConfigPointer target; + ConfigPointer defvalue; + ConfigSetting constraint1; + ConfigSetting constraint2; + int restartFlags; + ConfigOption *next; +}; + +typedef struct { + int currentOffset; + int nextOffset; + int leapType; + Integer32 startTime; + Integer32 endTime; + Boolean valid; + Boolean offsetValid; +} LeapSecondInfo; + +#endif /*DATATYPES_DEP_H_*/ diff --git a/src/ptpd/src/dep/eventtimer.c b/src/ptpd/src/dep/eventtimer.c new file mode 100644 index 0000000..de98338 --- /dev/null +++ b/src/ptpd/src/dep/eventtimer.c @@ -0,0 +1,133 @@ +/*- + * Copyright (c) 2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file timer.c + * @date Wed Oct 1 00:41:26 2014 + * + * @brief Common code for the EventTimer object + * + * Creation and deletion plus maintaining a linked list of + * all created instances. + */ + +#include "../ptpd.h" + +/* linked list - so that we can control all registered objects centrally */ +static EventTimer *_first = NULL; +static EventTimer *_last = NULL; + +EventTimer +*createEventTimer(const char* id) +{ + + EventTimer *timer; + + if ( !(timer = calloc (1, sizeof(EventTimer))) ) { + return NULL; + } + + + setupEventTimer(timer); + + strncpy(timer->id, id, EVENTTIMER_MAX_DESC); + + /* maintain the linked list */ + + if(_first == NULL) { + _first = timer; + } + + if(_last != NULL) { + timer->_prev = _last; + timer->_prev->_next = timer; + } + + _last = timer; + + timer->_first = _first; + + DBGV("created itimer eventtimer %s\n", timer->id); + + return timer; +} + +void +freeEventTimer +(EventTimer **timer) +{ + if(timer == NULL) { + return; + } + + EventTimer *ptimer = *timer; + + if(ptimer == NULL) { + return; + } + + ptimer->shutdown(ptimer); + + /* maintain the linked list */ + + if(ptimer->_prev != NULL) { + + if(ptimer == _last) { + _last = ptimer->_prev; + } + + if(ptimer->_next != NULL) { + ptimer->_prev->_next = ptimer->_next; + } else { + ptimer->_prev->_next = NULL; + } + /* last one */ + } else if (ptimer->_next == NULL) { + _first = NULL; + } + + if(ptimer->_next != NULL) { + + if(ptimer == _first) { + _first = ptimer->_next; + } + + if(ptimer->_prev != NULL) { + ptimer->_next->_prev = ptimer->_prev; + } else { + ptimer->_next->_prev = NULL; + } + + } + + if(*timer != NULL) { + free(*timer); + } + + *timer = NULL; + +} diff --git a/src/ptpd/src/dep/eventtimer.h b/src/ptpd/src/dep/eventtimer.h new file mode 100644 index 0000000..9d41156 --- /dev/null +++ b/src/ptpd/src/dep/eventtimer.h @@ -0,0 +1,77 @@ +#ifndef EVENTTIMER_H_ +#define EVENTTIMER_H_ + +#include "../ptpd.h" + +/*- + * Copyright (c) 2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define EVENTTIMER_MAX_DESC 20 +#define EVENTTIMER_MIN_INTERVAL_US 250 /* 4000/sec */ + +typedef struct EventTimer EventTimer; + +struct EventTimer { + + /* data */ + char id[EVENTTIMER_MAX_DESC + 1]; + Boolean expired; + Boolean running; + + /* "methods" */ + void (*start) (EventTimer* timer, double interval); + void (*stop) (EventTimer* timer); + void (*reset) (EventTimer* timer); + void (*shutdown) (EventTimer* timer); + Boolean (*isExpired) (EventTimer* timer); + Boolean (*isRunning) (EventTimer* timer); + + /* implementation data */ +#ifdef PTPD_PTIMERS + timer_t timerId; +#else + int32_t itimerInterval; + int32_t itimerLeft; +#endif /* PTPD_PTIMERS */ + + /* linked list */ + EventTimer *_first; + EventTimer *_next; + EventTimer *_prev; + +}; + +EventTimer *createEventTimer(const char *id); +void freeEventTimer(EventTimer **timer); +void setupEventTimer(EventTimer *timer); + +void startEventTimers(); +void shutdownEventTimers(); + + +#endif /* EVENTTIMER_H_ */ + diff --git a/src/ptpd/src/dep/eventtimer_itimer.c b/src/ptpd/src/dep/eventtimer_itimer.c new file mode 100644 index 0000000..8553fe7 --- /dev/null +++ b/src/ptpd/src/dep/eventtimer_itimer.c @@ -0,0 +1,239 @@ +/*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file eventtimer_itimer.c + * @date Wed Oct 1 00:41:26 2014 + * + * @brief EventTimer implementation using interval timers + * + * The original interval timer timer implementation. + */ + +#include "../ptpd.h" + +#define US_TIMER_INTERVAL (31250) + +static volatile unsigned int elapsed; + +static void eventTimerStart_itimer(EventTimer *timer, double interval); +static void eventTimerStop_itimer(EventTimer *timer); +static void eventTimerReset_itimer(EventTimer *timer); +static void eventTimerShutdown_itimer(EventTimer *timer); +static Boolean eventTimerIsRunning_itimer(EventTimer *timer); +static Boolean eventTimerIsExpired_itimer(EventTimer *timer); + +static void itimerUpdate(EventTimer *et); +static void timerSignalHandler(int sig); + +void +setupEventTimer(EventTimer *timer) +{ + + if(timer == NULL) { + return; + } + + memset(timer, 0, sizeof(EventTimer)); + + timer->start = eventTimerStart_itimer; + timer->stop = eventTimerStop_itimer; + timer->reset = eventTimerReset_itimer; + timer->shutdown = eventTimerShutdown_itimer; + timer->isExpired = eventTimerIsExpired_itimer; + timer->isRunning = eventTimerIsRunning_itimer; +} + +static void +eventTimerStart_itimer(EventTimer *timer, double interval) +{ + + timer->expired = FALSE; + timer->running = TRUE; + + /* + * US_TIMER_INTERVAL defines the minimum interval between sigalarms. + * timerStart has a float parameter for the interval, which is casted to integer. + * very small amounts are forced to expire ASAP by setting the interval to 1 + */ + timer->itimerLeft = (interval * 1E6) / US_TIMER_INTERVAL; + if(timer->itimerLeft == 0){ + /* + * the interval is too small, raise it to 1 to make sure it expires ASAP + */ + timer->itimerLeft = 1; + } + + timer->itimerInterval = timer->itimerLeft; + + DBG2("timerStart: Set timer %s to %f New interval: %d; new left: %d\n", timer->id, interval, timer->itimerLeft , timer->itimerInterval); +} + +static void +eventTimerStop_itimer(EventTimer *timer) +{ + + timer->itimerInterval = 0; + timer->running = FALSE; + DBG2("timerStop: Stopping timer %s\n", timer->id); + +} + +static void +itimerUpdate(EventTimer *et) +{ + + EventTimer *timer = NULL; + + if (elapsed <= 0) + return; + + /* + * if time actually passed, then decrease every timer left + * the one(s) that went to zero or negative are: + * a) rearmed at the original time (ignoring the time that may have passed ahead) + * b) have their expiration latched until timerExpired() is called + */ + + for(timer = et->_first; timer != NULL; timer = timer->_next) { + if ( (timer->itimerInterval > 0) && ((timer->itimerLeft -= elapsed) <= 0)) { + timer->itimerLeft = timer->itimerInterval; + timer->expired = TRUE; + DBG("TimerUpdate: Timer %s has now expired. Re-armed with interval %d\n", timer->id, timer->itimerInterval); + } + } + + elapsed = 0; + +} + + + +static void +eventTimerReset_itimer(EventTimer *timer) +{ +} + +static void +eventTimerShutdown_itimer(EventTimer *timer) +{ +} + + +static Boolean +eventTimerIsRunning_itimer(EventTimer *timer) +{ + + itimerUpdate(timer); + + DBG2("timerIsRunning: Timer %s %s running\n", timer->id, + timer->running ? "is" : "is not"); + + return timer->running; +} + +static Boolean +eventTimerIsExpired_itimer(EventTimer *timer) +{ + + Boolean ret; + + itimerUpdate(timer); + + ret = timer->expired; + + DBG2("timerIsExpired: Timer %s %s expired\n", timer->id, + timer->expired ? "is" : "is not"); + + if(ret) { + timer->expired = FALSE; + } + + return ret; + +} + +void +startEventTimers(void) +{ + struct itimerval itimer; + + DBG("initTimer\n"); + +#ifdef __sun + sigset(SIGALRM, SIG_IGN); +#else + signal(SIGALRM, SIG_IGN); +#endif /* __sun */ + + elapsed = 0; + itimer.it_value.tv_sec = itimer.it_interval.tv_sec = 0; + itimer.it_value.tv_usec = itimer.it_interval.tv_usec = + US_TIMER_INTERVAL; + +#ifdef __sun + sigset(SIGALRM, timerSignalHandler); +#else + signal(SIGALRM, timerSignalHandler); +#endif /* __sun */ + setitimer(ITIMER_REAL, &itimer, 0); +} + +void +shutdownEventTimers(void) +{ + +#ifdef __sun + sigset(SIGALRM, SIG_IGN); +#else + signal(SIGALRM, SIG_IGN); +#endif /* __sun */ +} + +static void +timerSignalHandler(int sig) +{ + elapsed++; + /* be sure to NOT call DBG in asynchronous handlers! */ +} diff --git a/src/ptpd/src/dep/eventtimer_posix.c b/src/ptpd/src/dep/eventtimer_posix.c new file mode 100644 index 0000000..16164fd --- /dev/null +++ b/src/ptpd/src/dep/eventtimer_posix.c @@ -0,0 +1,239 @@ +/*- + * Copyright (c) 2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file eventtimer_posix.c + * @date Wed Oct 1 00:41:26 2014 + * + * @brief EventTimer implementation using POSIX timers + * + * The less signal intensive timer implementation, + * allowing for high(er) message intervals. + */ + +#include "../ptpd.h" + +#define TIMER_SIGNAL SIGALRM + +/* timers should be based on CLOCK_MONOTONIC, + * but if it's not implemented, use CLOCK_REALTIME + */ + +#ifdef _POSIX_MONOTONIC_CLOCK +#define CLK_TYPE CLOCK_MONOTONIC +#else +#define CLK_TYPE CLOCK_REALTIME +#endif /* CLOCK_MONOTONIC */ + +static void eventTimerStart_posix(EventTimer *timer, double interval); +static void eventTimerStop_posix(EventTimer *timer); +static void eventTimerReset_posix(EventTimer *timer); +static void eventTimerShutdown_posix(EventTimer *timer); +static Boolean eventTimerIsRunning_posix(EventTimer *timer); +static Boolean eventTimerIsExpired_posix(EventTimer *timer); +static void timerSignalHandler(int sig, siginfo_t *info, void *usercontext); + +void +setupEventTimer(EventTimer *timer) +{ + + struct sigevent sev; + + if(timer == NULL) { + return; + } + + memset(&sev, 0, sizeof(sev)); + memset(timer, 0, sizeof(EventTimer)); + + timer->start = eventTimerStart_posix; + timer->stop = eventTimerStop_posix; + timer->reset = eventTimerReset_posix; + timer->shutdown = eventTimerShutdown_posix; + timer->isExpired = eventTimerIsExpired_posix; + timer->isRunning = eventTimerIsRunning_posix; + + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = TIMER_SIGNAL; + sev.sigev_value.sival_ptr = timer; + + if(timer_create(CLK_TYPE, &sev, &timer->timerId) == -1) { + PERROR("Could not create posix timer %s", timer->id); + } else { + DBGV("Created posix timer %s ",timer->id); + } +} + +static void +eventTimerStart_posix(EventTimer *timer, double interval) +{ + + struct timespec ts; + struct itimerspec its; + + memset(&its, 0, sizeof(its)); + + ts.tv_sec = interval; + ts.tv_nsec = (interval - ts.tv_sec) * 1E9; + + if(!ts.tv_sec && ts.tv_nsec < EVENTTIMER_MIN_INTERVAL_US * 1000) { + ts.tv_nsec = EVENTTIMER_MIN_INTERVAL_US * 1000; + } + + DBGV("Timer %s start requested at %d.%4d sec interval\n", timer->id, ts.tv_sec, ts.tv_nsec); + + its.it_interval = ts; + its.it_value = ts; + + if (timer_settime(timer->timerId, 0, &its, NULL) < 0) { + PERROR("could not arm posix timer %s", timer->id); + return; + } + + DBG2("timerStart: Set timer %s to %f\n", timer->id, interval); + + timer->expired = FALSE; + timer->running = TRUE; + +} + +static void +eventTimerStop_posix(EventTimer *timer) +{ + + struct itimerspec its; + + DBGV("Timer %s stop requested\n", timer->id); + + memset(&its, 0, sizeof(its)); + + if (timer_settime(timer->timerId, 0, &its, NULL) < 0) { + PERROR("could not stop posix timer %s", timer->id); + return; + } + + timer->running = FALSE; + + DBG2("timerStop: stopped timer %s\n", timer->id); + +} + +static void +eventTimerReset_posix(EventTimer *timer) +{ +} + +static void +eventTimerShutdown_posix(EventTimer *timer) +{ + if(timer_delete(timer->timerId) == -1) { + PERROR("Could not delete timer %s!", timer->id); + } + +} + +static Boolean +eventTimerIsRunning_posix(EventTimer *timer) +{ + + DBG2("timerIsRunning: Timer %s %s running\n", timer->id, + timer->running ? "is" : "is not"); + + return timer->running; +} + +static Boolean +eventTimerIsExpired_posix(EventTimer *timer) +{ + + Boolean ret; + + ret = timer->expired; + + DBG2("timerIsExpired: Timer %s %s expired\n", timer->id, + timer->expired ? "is" : "is not"); + + /* the five monkeys experiment */ + if(ret) { + timer->expired = FALSE; + } + + return ret; + +} + +void +startEventTimers(void) +{ + struct sigaction sa; + + DBG("initTimer\n"); + +#ifdef __sun + sigset(SIGALRM, SIG_IGN); +#else + signal(SIGALRM, SIG_IGN); +#endif /* __sun */ + + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = timerSignalHandler; + sigemptyset(&sa.sa_mask); + if(sigaction(TIMER_SIGNAL, &sa, NULL) == -1) { + PERROR("Could not initialise timer handler"); + } + +} + +void +shutdownEventTimers(void) +{ + +#ifdef __sun + sigset(SIGALRM, SIG_IGN); +#else + signal(SIGALRM, SIG_IGN); +#endif /* __sun */ +} + +/* this only ever gets called when a timer expires */ +static void +timerSignalHandler(int sig, siginfo_t *info, void *usercontext) +{ + + /* retrieve the user data structure */ + EventTimer * timer = (EventTimer*)info->si_value.sival_ptr; + + /* Ignore if the signal wasn't sent by a timer */ + if(info->si_code != SI_TIMER) + return; + + /* Hopkirk (deceased) */ + if(timer != NULL) { + timer->expired = TRUE; + } + +} diff --git a/src/ptpd/src/dep/iniparser/AUTHORS b/src/ptpd/src/dep/iniparser/AUTHORS new file mode 100644 index 0000000..f847f92 --- /dev/null +++ b/src/ptpd/src/dep/iniparser/AUTHORS @@ -0,0 +1,7 @@ +Author: Nicolas Devillard + +This tiny library has received countless contributions and I have +not kept track of all the people who contributed. Let them be thanked +for their ideas, code, suggestions, corrections, enhancements! + +Further enhancements by Wojciech Owczarek for the PTPd project diff --git a/src/ptpd/src/dep/iniparser/LICENSE b/src/ptpd/src/dep/iniparser/LICENSE new file mode 100644 index 0000000..5a3a80b --- /dev/null +++ b/src/ptpd/src/dep/iniparser/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2000-2011 by Nicolas Devillard. +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/src/ptpd/src/dep/iniparser/README b/src/ptpd/src/dep/iniparser/README new file mode 100644 index 0000000..bc69787 --- /dev/null +++ b/src/ptpd/src/dep/iniparser/README @@ -0,0 +1,12 @@ + +Welcome to iniParser -- version 3.1 +released 08 Apr 2012 + +This modules offers parsing of ini files from the C level. +See a complete documentation in HTML format, from this directory +open the file html/index.html with any HTML-capable browser. + +Enjoy! + +N.Devillard +Sun Apr 8 16:38:09 CEST 2012 diff --git a/src/ptpd/src/dep/iniparser/dictionary.c b/src/ptpd/src/dep/iniparser/dictionary.c new file mode 100644 index 0000000..ffe3579 --- /dev/null +++ b/src/ptpd/src/dep/iniparser/dictionary.c @@ -0,0 +1,520 @@ +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.c + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ +#include "dictionary.h" + +#include +#include +#include +#include + +/** Maximum value size for integers and doubles. */ +#define MAXVALSZ 1024 + +/** Minimal allocated number of entries in a dictionary */ +#define DICTMINSZ 64 + +/** Invalid key token */ +#define DICT_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private functions + ---------------------------------------------------------------------------*/ + +/* Doubles the allocated size associated to a pointer */ +/* 'size' is the current allocated size. */ +static void * mem_double(void * ptr, int size) +{ + void * newptr ; + + newptr = calloc(2*size, 1); + if (newptr==NULL) { + return NULL ; + } + memcpy(newptr, ptr, size); + free(ptr); + return newptr ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + if (!s) + return NULL ; + t = (char*)calloc(strlen(s)+1,sizeof(char)) ; + if (t) { + strncpy(t,s,strlen(s)); + } + return t ; +} + +/*--------------------------------------------------------------------------- + Function codes + ---------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key) +{ + int len ; + unsigned hash ; + int i ; + + len = strlen(key); + for (hash=0, i=0 ; i>6) ; + } + hash += (hash <<3); + hash ^= (hash >>11); + hash += (hash <<15); + return hash ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary objet. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*--------------------------------------------------------------------------*/ +dictionary * dictionary_new(int size) +{ + dictionary * d ; + + /* If no size was specified, allocate space for DICTMINSZ */ + if (sizesize = size ; + d->val = (char **)calloc(size, sizeof(char*)); + d->key = (char **)calloc(size, sizeof(char*)); + d->hash = (unsigned int *)calloc(size, sizeof(unsigned)); + return d ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary ** d) +{ + int i ; + + if (*d==NULL) return ; + for (i=0 ; i<(*d)->size ; i++) { + if ((*d)->key[i]!=NULL) + free((*d)->key[i]); + if ((*d)->val[i]!=NULL) + free((*d)->val[i]); + } + free((*d)->val); + free((*d)->key); + free((*d)->hash); + free(*d); + *d = NULL; + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +char * dictionary_get(dictionary * d, const char * key, char * def) +{ + unsigned hash ; + int i ; + + hash = dictionary_hash(key); + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + return d->val[i] ; + } + } + } + return def ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * d, const char * key, const char * val) +{ + int i ; + unsigned hash ; + + if (d==NULL || key==NULL) return -1 ; + + /* Compute hash for this key */ + hash = dictionary_hash(key) ; + /* Find if value is already in dictionary */ + if (d->n>0) { + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (hash==d->hash[i]) { /* Same hash value */ + if (!strcmp(key, d->key[i])) { /* Same key */ + /* Found a value: modify and return */ + if (d->val[i]!=NULL) + free(d->val[i]); + d->val[i] = val ? xstrdup(val) : NULL ; + /* Value has been modified: return */ + return 0 ; + } + } + } + } + /* Add a new value */ + /* See if dictionary needs to grow */ + if (d->n==d->size) { + + /* Reached maximum size: reallocate dictionary */ + d->val = (char **)mem_double(d->val, d->size * sizeof(char*)) ; + d->key = (char **)mem_double(d->key, d->size * sizeof(char*)) ; + d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ; + if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) { + /* Cannot grow dictionary */ + return -1 ; + } + /* Double size */ + d->size *= 2 ; + } + + /* Insert key in the first empty slot. Start at d->n and wrap at + d->size. Because d->n < d->size this will necessarily + terminate. */ + for (i=d->n ; d->key[i] ; ) { + if(++i == d->size) i = 0; + } + /* Copy key */ + d->key[i] = xstrdup(key); + d->val[i] = val ? xstrdup(val) : NULL ; + d->hash[i] = hash; + d->n ++ ; + + return 0 ; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key) +{ + unsigned hash ; + int i ; + + if (key == NULL) { + return; + } + + hash = dictionary_hash(key); + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + /* Found key */ + break ; + } + } + } + if (i>=d->size) + /* Key not found */ + return ; + + free(d->key[i]); + d->key[i] = NULL ; + if (d->val[i]!=NULL) { + free(d->val[i]); + d->val[i] = NULL ; + } + d->hash[i] = 0 ; + d->n -- ; + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Replace string in all values of a dictionary + @param d dictionary object to modify. + @param search string to be replaced + @param replace string to replace with + @return void + + This function globally replaces all occurrences of one string in the ditcionary + with another in key values. + + */ +/*--------------------------------------------------------------------------*/ +void dictionary_replace(dictionary * d, const char * search, const char * replace) +{ + + int bufsize = MAXVALSZ; + char out[bufsize+1]; + char *in = NULL; + char *val = NULL; + char *found = NULL; + int i = 0, j = 0; + int maxcount = 0; + char *pos = NULL; + char *data = NULL; + + if (search==NULL || replace==NULL ) return; + + for(i = 0; i < d->n; i++) { + + memset(out, 0, bufsize+1); + /* skip if the key is null or is a section */ + if(d->key[i] == NULL || strstr(d->key[i],":") == NULL) + continue; + + val = dictionary_get(d, d->key[i], ""); + data = strdup(val); + in = data; + found=strstr(in, search); + + if(found != NULL) { + do { + pos=found; + + for (j=0; j < strlen(search); j++) { + *pos='\0'; + pos++; + } + maxcount = bufsize - strlen(out); + maxcount = (maxcount < 0) ? 0 : maxcount; + strncat(out,in,maxcount); + + maxcount = bufsize - strlen(out); + maxcount = (maxcount < 0) ? 0 : maxcount; + strncat(out,replace,maxcount); + + in = pos; + + found=strstr(in, search); + + } while (found != NULL); + + if(*pos != 0) { + maxcount = bufsize - strlen(out); + maxcount = (maxcount < 0) ? 0 : maxcount; + strncat(out,pos,maxcount); + } + + dictionary_set(d, d->key[i], out); +/* + printf("Replaced token \"%s\" with \"%s\": \"%s\" -> \"%s\"\n", + search, replace, val, out); +*/ + } + free(data); + + } + + +} + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(dictionary * d, FILE * out) +{ + int i ; + + if (d==NULL || out==NULL) return ; + if (d->n<1) { + fprintf(out, "empty dictionary\n"); + return ; + } + for (i=0 ; isize ; i++) { + if (d->key[i]) { + fprintf(out, "%20s\t[%s]\n", + d->key[i], + d->val[i] ? d->val[i] : "UNDEF"); + } + } + return ; +} + +int dictionary_merge(dictionary* source, dictionary* dest, int overwrite, int warn, const char* warnStr) +{ + + int i = 0; + int clobber = 1; + + if( source == NULL || dest == NULL) return -1; + + if(warnStr == NULL) warnStr = ""; + for(i = 0; i < source->n; i++) { + clobber = 1; + if(source->key[i] == NULL) + continue; + /* do not overwrite with an empty key */ + if((source->val[i] == NULL) || (strlen(source->val[i])==0)) + continue; + /* no need to warn for settings whose value will not change */ + if((strcmp(dictionary_get(dest,source->key[i],""),"") != 0) && + (strcmp(dictionary_get(dest,source->key[i],""),source->val[i]) != 0)) { + if(overwrite && warn) { + WARNING("Warning: %s=\"%s\" : value \"%s\" takes priority %s\n", + source->key[i], dictionary_get(dest,source->key[i],""), + source->val[i], warnStr); + } + if(!overwrite) { + clobber = 0; + } + } + if (clobber) { + if(dictionary_set( dest, source->key[i], source->val[i]) != 0) { + return -1; + } + } + } + + return 0; +} + +/* Test code */ +#ifdef TESTDIC +#define NVALS 20000 +int main(int argc, char *argv[]) +{ + dictionary * d ; + char * val ; + int i ; + char cval[90] ; + + /* Allocate dictionary */ + printf("allocating...\n"); + d = dictionary_new(0); + + /* Set values in dictionary */ + printf("setting %d values...\n", NVALS); + for (i=0 ; in != 0) { + printf("error deleting values\n"); + } + printf("deallocating...\n"); + dictionary_del(&d); + return 0 ; +} +#endif +/* vim: set ts=4 et sw=4 tw=75 */ diff --git a/src/ptpd/src/dep/iniparser/dictionary.h b/src/ptpd/src/dep/iniparser/dictionary.h new file mode 100644 index 0000000..cf1027e --- /dev/null +++ b/src/ptpd/src/dep/iniparser/dictionary.h @@ -0,0 +1,189 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.h + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _DICTIONARY_H_ +#define _DICTIONARY_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include + +void logMessage(int priority, const char *format, ...); + +#ifndef WARNING +#define WARNING(x, ...) logMessage(LOG_WARNING, x, ##__VA_ARGS__) +#endif /* WARNING */ + +#include +#include +#include +#include + +/*--------------------------------------------------------------------------- + New types + ---------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dictionary object + + This object contains a list of string/string associations. Each + association is identified by a unique string key. Looking up values + in the dictionary is speeded up by the use of a (hopefully collision-free) + hash function. + */ +/*-------------------------------------------------------------------------*/ +typedef struct _dictionary_ { + int n ; /** Number of entries in dictionary */ + int size ; /** Storage size */ + char ** val ; /** List of string values */ + char ** key ; /** List of string keys */ + unsigned * hash ; /** List of hash values for keys */ +} dictionary ; + + +/*--------------------------------------------------------------------------- + Function prototypes + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key); + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary objet. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*--------------------------------------------------------------------------*/ +dictionary * dictionary_new(int size); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary ** vd); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +char * dictionary_get(dictionary * d, const char * key, char * def); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * vd, const char * key, const char * val); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key); + +/*-------------------------------------------------------------------------*/ +/** + @brief Replace string in all values of a dictionary + @param d dictionary object to modify. + @param search string to be replaced + @param replace string to replace with + @return void + + This function globally replaces all occurrences of one string in the ditcionary + with another in key values. + + */ +/*--------------------------------------------------------------------------*/ +void dictionary_replace(dictionary * d, const char * search, const char * replace); + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(dictionary * d, FILE * out); + +int dictionary_merge(dictionary * source, dictionary * dest, int overwrite, int warn, const char* warnStr); + +#endif diff --git a/src/ptpd/src/dep/iniparser/iniparser.c b/src/ptpd/src/dep/iniparser/iniparser.c new file mode 100644 index 0000000..04443e5 --- /dev/null +++ b/src/ptpd/src/dep/iniparser/iniparser.c @@ -0,0 +1,771 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.c + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ +/*---------------------------- Includes ------------------------------------*/ +#include +#include "iniparser.h" + +/*---------------------------- Defines -------------------------------------*/ +#define ASCIILINESZ (1024) +#define INI_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private to this module + ---------------------------------------------------------------------------*/ +/** + * This enum stores the status for each parsed line (internal use only). + */ +typedef enum _line_status_ { + LINE_UNPROCESSED, + LINE_ERROR, + LINE_EMPTY, + LINE_COMMENT, + LINE_SECTION, + LINE_VALUE +} line_status ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Convert a string to lowercase. + @param s String to convert. + @return ptr to statically allocated string. + + This function returns a pointer to a statically allocated string + containing a lowercased version of the input string. Do not free + or modify the returned string! Since the returned string is statically + allocated, it will be modified at each function call (not re-entrant). + */ +/*--------------------------------------------------------------------------*/ +static char * strlwc(const char * s) +{ + static char l[ASCIILINESZ+1]; + int i ; + + if (s==NULL) return NULL ; + memset(l, 0, ASCIILINESZ+1); + i=0 ; + while (s[i] && i l) { + if (!isspace((int)*(last-1))) + break ; + last -- ; + } + *last = (char)0; + return (char*)l ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getnsec(dictionary * d) +{ + int i ; + int nsec ; + + if (d==NULL) return -1 ; + nsec=0 ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + nsec ++ ; + } + } + return nsec ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ +char * iniparser_getsecname(dictionary * d, int n) +{ + int i ; + int foundsec ; + + if (d==NULL || n<0) return NULL ; + foundsec=0 ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + foundsec++ ; + if (foundsec>n) + break ; + } + } + if (foundsec<=n) { + return NULL ; + } + return d->key[i] ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(dictionary * d, FILE * f) +{ + int i ; + + if (d==NULL || f==NULL) return ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (d->val[i]!=NULL) { + fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); + } else { + fprintf(f, "[%s]=UNDEF\n", d->key[i]); + } + } + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump_ini(dictionary * d, FILE * f) +{ + int i ; + int nsec ; + char * secname ; + + if (d==NULL || f==NULL) return ; + + nsec = iniparser_getnsec(d); + if (nsec<1) { + /* No section in file: dump all keys as they are */ + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + fprintf(f, "%s = %s\n", d->key[i], d->val[i]); + } + return ; + } + for (i=0 ; isize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + fprintf(f, + "%-30s = %s\n", + d->key[j]+seclen+1, + d->val[j] ? d->val[j] : ""); + } + } + fprintf(f, "\n"); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(dictionary * d, char * s) +{ + int seclen, nkeys ; + char keym[ASCIILINESZ+1]; + int j ; + + nkeys = 0; + + if (d==NULL) return nkeys; + if (! iniparser_find_entry(d, s)) return nkeys; + + seclen = (int)strlen(s); + snprintf(keym, ASCIILINESZ, "%s:", s); + + for (j=0 ; jsize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) + nkeys++; + } + + return nkeys; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return pointer to statically allocated character strings + + This function queries a dictionary and finds all keys in a given section. + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ +char ** iniparser_getseckeys(dictionary * d, char * s) +{ + + char **keys; + + int i, j ; + char keym[ASCIILINESZ+1]; + int seclen, nkeys ; + + keys = NULL; + + if (d==NULL) return keys; + if (! iniparser_find_entry(d, s)) return keys; + + nkeys = iniparser_getsecnkeys(d, s); + + keys = (char**) malloc(nkeys*sizeof(char*)); + + seclen = (int)strlen(s); + snprintf(keym, ASCIILINESZ, "%s:", s); + + i = 0; + + for (j=0 ; jsize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + keys[i] = d->key[j]; + i++; + } + } + + return keys; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +char * iniparser_getstring(dictionary * d, const char * key, char * def) +{ + char * lc_key ; + char * sval ; + + if (d==NULL || key==NULL) + return def ; + + lc_key = strlwc(key); + sval = dictionary_get(d, lc_key, def); + return sval ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(dictionary * d, const char * key, int notfound) +{ + char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==INI_INVALID_KEY) return notfound ; + return (int)strtol(str, NULL, 0); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(dictionary * d, const char * key, double notfound) +{ + char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==INI_INVALID_KEY) return notfound ; + return atof(str); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(dictionary * d, const char * key, int notfound) +{ + char * c ; + int ret ; + + c = iniparser_getstring(d, key, INI_INVALID_KEY); + if (c==INI_INVALID_KEY) return notfound ; + if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { + ret = 1 ; + } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { + ret = 0 ; + } else { + ret = notfound ; + } + return ret; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry( + dictionary * ini, + const char * entry +) +{ + int found=0 ; + if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { + found = 1 ; + } + return found ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, -1 is returned. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val) +{ + char section[ASCIILINESZ+1]; + if(sscanf(entry,":%[^:]:",section)>0) + dictionary_set(ini,section,NULL); + if(sscanf(entry,"%[^:]:",section)>0) + dictionary_set(ini,section,NULL); + return dictionary_set(ini, strlwc(entry), val) ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry) +{ + dictionary_unset(ini, strlwc(entry)); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Load a single line from an INI file + @param input_line Input line, may be concatenated multi-line input + @param section Output space to store section + @param key Output space to store key + @param value Output space to store value + @return line_status value + */ +/*--------------------------------------------------------------------------*/ +static line_status iniparser_line( + const char * input_line, + char * section, + char * key, + char * value) +{ + line_status sta ; + char line[ASCIILINESZ+1]; + int len ; + memset(line, 0, ASCIILINESZ + 1); + strncpy(line, strstrip(input_line), ASCIILINESZ); + len = (int)strlen(line); + + sta = LINE_UNPROCESSED ; + if (len<1) { + /* Empty line */ + sta = LINE_EMPTY ; + } else if (line[0]=='#' || line[0]==';') { + /* Comment line */ + sta = LINE_COMMENT ; + } else if (line[0]=='[' && line[len-1]==']') { + /* Section name */ + sscanf(line, "[%[^]]", section); + strncpy(section, strstrip(section), ASCIILINESZ); + strncpy(section, strlwc(section), ASCIILINESZ); + sta = LINE_SECTION ; + } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 + || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2 + || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { + /* Usual key=value, with or without comments */ + strncpy(key, strstrip(key), ASCIILINESZ); + strncpy(key, strlwc(key), ASCIILINESZ); + strncpy(value, strstrip(value), ASCIILINESZ); + /* + * sscanf cannot handle '' or "" as empty values + * this is done here + */ + if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { + value[0]=0 ; + } + sta = LINE_VALUE ; + } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 + || sscanf(line, "%[^=] %[=]", key, value) == 2) { + /* + * Special cases: + * key= + * key=; + * key=# + */ + strncpy(key, strstrip(key), ASCIILINESZ); + strncpy(key, strlwc(key), ASCIILINESZ); + value[0]=0 ; + sta = LINE_VALUE ; + } else { + /* Generate syntax error */ + sta = LINE_ERROR ; + } + return sta ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame) +{ + FILE * in ; + + char line [ASCIILINESZ+1] ; + char section [ASCIILINESZ+1] ; + char key [ASCIILINESZ+1] ; + char tmp [ASCIILINESZ+1] ; + char val [ASCIILINESZ+1] ; + + int last=0 ; + int len ; + int lineno=0 ; + int errs=0; + + dictionary * dict ; + + if ((in=fopen(ininame, "r"))==NULL) { + fprintf(stderr, "iniparser: cannot open %s\n", ininame); + return NULL ; + } + + dict = dictionary_new(0) ; + if (!dict) { + fclose(in); + return NULL ; + } + + memset(line, 0, ASCIILINESZ); + memset(section, 0, ASCIILINESZ); + memset(key, 0, ASCIILINESZ); + memset(val, 0, ASCIILINESZ); + memset(tmp, 0, ASCIILINESZ); + last=0 ; + + while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { + lineno++ ; + len = (int)strlen(line)-1; + if (len==0) + continue; + /* Safety check against buffer overflows */ + if (line[len]!='\n') { + fprintf(stderr, + "iniparser: input line too long or no newline in the end in %s (%d)\n", + ininame, + lineno); + dictionary_del(&dict); + fclose(in); + return NULL ; + } + /* Get rid of \n and spaces at end of line */ + while ((len>=0) && + ((line[len]=='\n') || (isspace((unsigned char)line[len])))) { + line[len]=0 ; + len-- ; + } + /* Detect multi-line */ + if (line[len]=='\\') { + /* Multi-line value */ + last=len ; + continue ; + } else { + last=0 ; + } + switch (iniparser_line(line, section, key, val)) { + case LINE_EMPTY: + case LINE_COMMENT: + break ; + + case LINE_SECTION: + errs = dictionary_set(dict, section, NULL); + break ; + + case LINE_VALUE: + snprintf(tmp, ASCIILINESZ, "%s%s%s", section, strlen(section)==0?"":":", key); + errs = dictionary_set(dict, tmp, val) ; + break ; + + case LINE_ERROR: + fprintf(stderr, "iniparser: syntax error in %s (%d):\n", + ininame, + lineno); + fprintf(stderr, "-> %s\n", line); + errs++ ; + break; + + default: + break ; + } + memset(line, 0, ASCIILINESZ); + last=0; + if (errs<0) { + fprintf(stderr, "iniparser: memory allocation failure\n"); + break ; + } + } + if (errs) { + dictionary_del(&dict); + dict = NULL ; + } + fclose(in); + return dict ; +} + + +int iniparser_merge_file(dictionary *dict, const char *filename, int overwrite) { + + dictionary *src; + + if ((src = iniparser_load(filename)) == NULL) { + return 0; + } + + dictionary_merge(src, dict, overwrite, 0, NULL); + + dictionary_del(&src); + + return 1; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary ** d) +{ + dictionary_del(d); +} + +/* vim: set ts=4 et sw=4 tw=75 */ diff --git a/src/ptpd/src/dep/iniparser/iniparser.h b/src/ptpd/src/dep/iniparser/iniparser.h new file mode 100644 index 0000000..a247ac6 --- /dev/null +++ b/src/ptpd/src/dep/iniparser/iniparser.h @@ -0,0 +1,311 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.h + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _INIPARSER_H_ +#define _INIPARSER_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include +#include +#include + +/* + * The following #include is necessary on many Unixes but not Linux. + * It is not needed for Windows platforms. + * Uncomment it if needed. + */ +/* #include */ + +#include "dictionary.h" + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ + +int iniparser_getnsec(dictionary * d); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ + +char * iniparser_getsecname(dictionary * d, int n); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dump_ini(dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(dictionary * d, char * s); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return pointer to statically allocated character strings + + This function queries a dictionary and finds all keys in a given section. + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ +char ** iniparser_getseckeys(dictionary * d, char * s); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +char * iniparser_getstring(dictionary * d, const char * key, char * def); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(dictionary * d, const char * key, int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(dictionary * d, const char * key, double notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(dictionary * d, const char * key, int notfound); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, -1 is returned. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry); + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(dictionary * ini, const char * entry) ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame); + +int iniparser_merge_file(dictionary *dict, const char * filename, int overwrite); + +dictionary * iniparser_load_list(const char* list); + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary ** d); + +#endif diff --git a/src/ptpd/src/dep/ipv4_acl.c b/src/ptpd/src/dep/ipv4_acl.c new file mode 100644 index 0000000..5b2bba3 --- /dev/null +++ b/src/ptpd/src/dep/ipv4_acl.c @@ -0,0 +1,486 @@ +/*- + * Copyright (c) 2013-2014 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ipv4_acl.c + * @date Sun Oct 13 21:02:23 2013 + * + * @brief Code to handle IPv4 access control lists + * + * Functions in this file parse, create and match IPv4 ACLs. + * + */ + +#include "../ptpd.h" +#include "string.h" + +/** + * strdup + free are used across code using strtok_r, so as to + * protect the original string, as strtok* modify it. + */ + +/* count tokens in string delimited by delim */ +static int countTokens(const char* text, const char* delim) { + + int count=0; + char* stash = NULL; + char* text_; + char* text__; + + if(text==NULL || delim==NULL) + return 0; + + text_=strdup(text); + + for(text__=text_,count=0;strtok_r(text__,delim, &stash) != NULL; text__=NULL) { + count++; + } + free(text_); + return count; + +} + +/* Parse a dotted-decimal string into an uint8_t array - return -1 on error */ +static int ipToArray(const char* text, uint8_t dest[], int maxOctets, Boolean isMask) +{ + + char* text_; + char* text__; + char* subtoken; + char* stash = NULL; + char* endptr; + long octet; + + int count = 0; + int result = 0; + + memset(&dest[0], 0, maxOctets * sizeof(uint8_t)); + + text_=strdup(text); + + for(text__=text_;;text__=NULL) { + + if(count > maxOctets) { + result = -1; + goto end; + } + + subtoken = strtok_r(text__,".",&stash); + + if(subtoken == NULL) + goto end; + + errno = 0; + octet=strtol(subtoken,&endptr,10); + if(errno!=0 || !IN_RANGE(octet,0,255)) { + result = -1; + goto end; + + } + + dest[count++] = (uint8_t)octet; + + /* If we're parsing a mask and an octet is less than 0xFF, whole rest is zeros */ + if(isMask && octet < 255) + goto end; + } + + end: + + free(text_); + return result; + +} + +/* Parse a single net mask into an AclEntry */ +static int parseAclEntry(const char* line, AclEntry* acl) { + + int result = 1; + char* stash = NULL; + char* text_; + char* token; + char* endptr; + long octet = 0; + + uint8_t net_octets[4]; + uint8_t mask_octets[4]; + + if(line == NULL || acl == NULL) + return -1; + + if(countTokens(line,"/") == 0) + return -1; + + text_=strdup(line); + + token=strtok_r(text_,"/",&stash); + + if((countTokens(token,".") > 4) || + (countTokens(token,".") < 1) || + (ipToArray(token,net_octets,4,0) < 0)) { + result=-1; + goto end; + } + + acl->network = (net_octets[0] << 24) | (net_octets[1] << 16) | ( net_octets[2] << 8) | net_octets[3]; + + token=strtok_r(NULL,"/",&stash); + + if(token == NULL) { + acl->netmask=32; + acl->bitmask=~0; + } else if(countTokens(token,".") == 1) { + errno = 0; + octet = strtol(token,&endptr,10); + if(errno != 0 || !IN_RANGE(octet,0,32)) { + result = -1; + goto end; + } + + if(octet == 0) + acl->bitmask = 0; + else + acl->bitmask = ~0 << (32 - octet); + acl->netmask = (uint16_t)octet; + + } else if((countTokens(token,".") > 4) || + (ipToArray(token,mask_octets,4,1) < 0)) { + result=-1; + goto end; + } else { + + acl->bitmask = (mask_octets[0] << 24) | (mask_octets[1] << 16) | ( mask_octets[2] << 8) | mask_octets[3]; + uint32_t tmp = acl->bitmask; + int count = 0; + for(tmp = acl->bitmask;tmp<netmask=count; + } + + end: + free(text_); + + acl->network &= acl->bitmask; + + return result; + +} + + +/* qsort() compare function for sorting ACL entries */ +static int +cmpAclEntry(const void *p1, const void *p2) +{ + + const AclEntry *left = p1; + const AclEntry *right = p2; + + if(left->network > right->network) + return 1; + if(left->network < right->network) + return -1; + return 0; + +} + +/* Parse an ACL string into an aclEntry table and return number of entries. output can be NULL */ +int +maskParser(const char* input, AclEntry* output) +{ + + char* token; + char* stash; + int found = 0; + char* text_; + char* text__; + AclEntry tmp; + + tmp.hitCount=0; + + if(strlen(input)==0) return 0; + + text_=strdup(input); + + for(text__=text_;;text__=NULL) { + + token=strtok_r(text__,", ;\t",&stash); + if(token==NULL) break; + + if(parseAclEntry(token,&tmp)<1) { + + found = -1; + break; + + } + + if(output != NULL) + output[found]=tmp; + + found++; + + + } + + if(found && (output != NULL)) + qsort(output, found, sizeof(AclEntry), cmpAclEntry); + + /* We got input but found nothing - error */ + if (!found) + found = -1; + + free(text_); + return found; + +} + +/* Create a maskTable from a text ACL */ +static MaskTable* +createMaskTable(const char* input) +{ + MaskTable* ret; + int masksFound = maskParser(input, NULL); + if(masksFound>=0) { + ret=(MaskTable*)calloc(1,sizeof(MaskTable)); + ret->entries = (AclEntry*)calloc(masksFound, sizeof(AclEntry)); + ret->numEntries = maskParser(input,ret->entries); + return ret; + } else { + ERROR("Error while parsing access list: \"%s\"\n", input); + return NULL; + } +} + +/* Print the contents of a single mask table */ +static void +dumpMaskTable(MaskTable* table) +{ + int i; + uint32_t network; + if(table == NULL) + return; + INFO("number of entries: %d\n",table->numEntries); + if(table->entries != NULL) { + for(i = 0; i < table->numEntries; i++) { + AclEntry this = table->entries[i]; +#ifdef PTPD_MSBF + network = this.network; +#else + network = htonl(this.network); +#endif + INFO("%d.%d.%d.%d/%d\t(0x%.8x/0x%.8x), matches: %d\n", + *((uint8_t*)&network), *((uint8_t*)&network+1), + *((uint8_t*)&network+2), *((uint8_t*)&network+3), this.netmask, + this.network, this.bitmask, this.hitCount); + } + } + +} + +/* Free a MaskTable structure */ +static void freeMaskTable(MaskTable** table) +{ + if(*table == NULL) + return; + + if((*table)->entries != NULL) { + free((*table)->entries); + (*table)->entries = NULL; + } + free(*table); + *table = NULL; +} + +/* Destroy an Ipv4AccessList structure */ +void +freeIpv4AccessList(Ipv4AccessList** acl) +{ + if(*acl == NULL) + return; + + freeMaskTable(&(*acl)->permitTable); + freeMaskTable(&(*acl)->denyTable); + + free(*acl); + *acl = NULL; +} + +/* Structure initialisation for Ipv4AccessList */ +Ipv4AccessList* +createIpv4AccessList(const char* permitList, const char* denyList, int processingOrder) +{ + Ipv4AccessList* ret; + ret = (Ipv4AccessList*)calloc(1,sizeof(Ipv4AccessList)); + ret->permitTable = createMaskTable(permitList); + ret->denyTable = createMaskTable(denyList); + if(ret->permitTable == NULL || ret->denyTable == NULL) { + freeIpv4AccessList(&ret); + return NULL; + } + ret->processingOrder = processingOrder; + return ret; +} + + +/* Match an IP address against a MaskTable */ +static int +matchAddress(const uint32_t addr, MaskTable* table) +{ + + int i; + if(table == NULL || table->entries == NULL || table->numEntries==0) + return -1; + for(i = 0; i < table->numEntries; i++) { + DBGV("addr: %08x, addr & mask: %08x, network: %08x\n",addr, table->entries[i].bitmask & addr, table->entries[i].network); + if((table->entries[i].bitmask & addr) == table->entries[i].network) { + table->entries[i].hitCount++; + return 1; + } + } + + return 0; + +} + +/* Test an IP address against an ACL */ +int +matchIpv4AccessList(Ipv4AccessList* acl, const uint32_t addr) +{ + + int ret; + int matchPermit = 0; + int matchDeny = 0; + + /* Non-functional ACL permits everything */ + if(acl == NULL) { + ret = 1; + goto end; + } + + if(acl->permitTable != NULL) + matchPermit = matchAddress(addr,acl->permitTable) > 0; + + if(acl->denyTable != NULL) + matchDeny = matchAddress(addr,acl->denyTable) > 0; + + switch(acl->processingOrder) { + case ACL_PERMIT_DENY: + if(!matchPermit) { + ret = 0; + break; + } + if(matchDeny) { + ret = 0; + break; + } + ret = 1; + break; + default: + case ACL_DENY_PERMIT: + if (matchDeny && !matchPermit) { + ret = 0; + break; + } + else { + ret = 1; + break; + } + } + + end: + + if(ret) + acl->passedCounter++; + else + acl->droppedCounter++; + + return ret; + +} + +/* Dump the contents and hit counters of an ACL */ +void dumpIpv4AccessList(Ipv4AccessList* acl) +{ + + INFO("\n\n"); + if(acl == NULL) { + INFO("(uninitialised ACL)\n"); + return; + } + switch(acl->processingOrder) { + case ACL_DENY_PERMIT: + INFO("ACL order: deny,permit\n"); + INFO("Passed packets: %d, dropped packets: %d\n", + acl->passedCounter, acl->droppedCounter); + INFO("--------\n"); + INFO("Deny list:\n"); + dumpMaskTable(acl->denyTable); + INFO("--------\n"); + INFO("Permit list:\n"); + dumpMaskTable(acl->permitTable); + break; + case ACL_PERMIT_DENY: + default: + INFO("ACL order: permit,deny\n"); + INFO("Passed packets: %d, dropped packets: %d\n", + acl->passedCounter, acl->droppedCounter); + INFO("--------\n"); + INFO("Permit list:\n"); + dumpMaskTable(acl->permitTable); + INFO("--------\n"); + INFO("Deny list:\n"); + dumpMaskTable(acl->denyTable); + break; + } + INFO("\n\n"); +} + +/* Clear counters in a MaskTable */ +static void +clearMaskTableCounters(MaskTable* table) +{ + + int i, count; + if(table==NULL || table->numEntries==0) + return; + count = table->numEntries; + + for(i=0; ientries[i].hitCount = 0; + } + +} + +/* Clear ACL counter */ +void clearIpv4AccessListCounters(Ipv4AccessList* acl) { + + if(acl == NULL) + return; + acl->passedCounter=0; + acl->droppedCounter=0; + clearMaskTableCounters(acl->permitTable); + clearMaskTableCounters(acl->denyTable); + +} diff --git a/src/ptpd/src/dep/ipv4_acl.h b/src/ptpd/src/dep/ipv4_acl.h new file mode 100644 index 0000000..65f7cd1 --- /dev/null +++ b/src/ptpd/src/dep/ipv4_acl.h @@ -0,0 +1,52 @@ +/** + * @file ipv4_acl.h + * + * @brief definitions related to IPv4 access control list handling + * + */ + +#ifndef PTPD_IPV4_ACL_H_ +#define PTPD_IPV4_ACL_H_ + +#define IN_RANGE(num, min,max) \ + (num >= min && num <= max) + +enum { + ACL_PERMIT_DENY, + ACL_DENY_PERMIT +}; + +typedef struct { + uint32_t network; + uint32_t bitmask; + uint16_t netmask; + uint32_t hitCount; +} AclEntry; + +typedef struct { + int numEntries; + AclEntry* entries; +} MaskTable; + +typedef struct { + MaskTable* permitTable; + MaskTable* denyTable; + int processingOrder; + uint32_t passedCounter; + uint32_t droppedCounter; +} Ipv4AccessList; + +/* Parse string into AclEntry array */ +int maskParser(const char* input, AclEntry* output); +/* Destroy an Ipv4AccessList structure */ +void freeIpv4AccessList(Ipv4AccessList** acl); +/* Initialise an Ipv4AccessList structure */ +Ipv4AccessList* createIpv4AccessList(const char* permitList, const char* denyList, int processingOrder); +/* Match on an IP address */ +int matchIpv4AccessList(Ipv4AccessList* acl, const uint32_t addr); +/* Display the contents and hit counters of an access list */ +void dumpIpv4AccessList(Ipv4AccessList* acl); +/* Clear counters */ +void clearIpv4AccessListCounters(Ipv4AccessList* acl); + +#endif /* PTPD_IPV4_ACL_H_ */ diff --git a/src/ptpd/src/dep/msg.c b/src/ptpd/src/dep/msg.c new file mode 100644 index 0000000..5b6afc0 --- /dev/null +++ b/src/ptpd/src/dep/msg.c @@ -0,0 +1,2957 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + /*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file msg.c + * @author George Neville-Neil + * @date Tue Jul 20 16:17:05 2010 + * + * @brief Functions to pack and unpack messages. + * + * See spec annex d + */ + +#include "../ptpd.h" +#include "../score/inc/score_ptp_subtlv.h" + +extern RunTimeOpts rtOpts; + +#define PACK_SIMPLE( type ) \ +void pack##type( void* from, void* to ) \ +{ \ + *(type *)to = *(type *)from; \ +} \ +void unpack##type( void* from, void* to, PtpClock *ptpClock ) \ +{ \ + pack##type( from, to ); \ +} + +#define PACK_ENDIAN( type, size ) \ +void pack##type( void* from, void* to ) \ +{ \ + *(type *)to = flip##size( *(type *)from ); \ +} \ +void unpack##type( void* from, void* to, PtpClock *ptpClock ) \ +{ \ + pack##type( from, to ); \ +} + +#define PACK_LOWER_AND_UPPER( type ) \ +void pack##type##Lower( void* from, void* to ) \ +{ \ + *(char *)to = *(char *)to & 0xF0; \ + *(char *)to = *(char *)to | *(type *)from; \ +} \ +\ +void pack##type##Upper( void* from, void* to ) \ +{ \ + *(char *)to = *(char *)to & 0x0F; \ + *(char *)to = *(char *)to | (*(type *)from << 4); \ +} \ +\ +void unpack##type##Lower( void* from, void* to, PtpClock *ptpClock ) \ +{ \ + *(type *)to = *(char *)from & 0x0F; \ +} \ +\ +void unpack##type##Upper( void* from, void* to, PtpClock *ptpClock ) \ +{ \ + *(type *)to = (*(char *)from >> 4) & 0x0F; \ +} + +PACK_SIMPLE( Boolean ) +PACK_SIMPLE( UInteger8 ) +PACK_SIMPLE( Octet ) +PACK_SIMPLE( Enumeration8 ) +PACK_SIMPLE( Integer8 ) + +PACK_ENDIAN( Enumeration16, 16 ) +PACK_ENDIAN( Integer16, 16 ) +PACK_ENDIAN( UInteger16, 16 ) +PACK_ENDIAN( Integer32, 32 ) +PACK_ENDIAN( UInteger32, 32 ) + +PACK_LOWER_AND_UPPER( Enumeration4 ) +PACK_LOWER_AND_UPPER( UInteger4 ) +PACK_LOWER_AND_UPPER( Nibble ) + +/* The free function is intentionally empty. However, this simplifies + * the procedure to deallocate complex data types + */ +#define FREE( type ) \ +void free##type( void* x) \ +{} + +FREE ( Boolean ) +FREE ( UInteger8 ) +FREE ( Octet ) +FREE ( Enumeration8 ) +FREE ( Integer8 ) +FREE ( Enumeration16 ) +FREE ( Integer16 ) +FREE ( UInteger16 ) +FREE ( Integer32 ) +FREE ( UInteger32 ) +FREE ( Enumeration4 ) +FREE ( UInteger4 ) +FREE ( Nibble ) + + +// ##### SCORE MODIFICATION BEGIN ##### + +#if SCORE_EXTENSIONS + +/** @brief Update the FUP information for IEEE 802.1AS and AUTOSAR + * @details + * This function is used by Provider to update all FUP information related to + * both IEEE 802.1AS and AUTOSAR. + * @param[out] fupInfoBufPtrU8 Pointer to buffer where FUP information shall be + * updated by this function. + * @param[in] bufSizeU8 Size of the buffer being passed. + * @return None. + */ +static void Score_PtpWriteFupInfoToBuffer(uint8_t *fupInfoBufPtrU8, uint8_t bufSizeU8); + +#endif +// ##### SCORE MODIFICATION END ##### + +static inline int bufGuard(int max, long base, int len, long beginning, int size); + +/* + * check if data we want to read is within the allocated buffer, + * and if it is within the message length given. + */ +static inline int +bufGuard(int max, long base, int len, long beginning, int size) +{ +#ifdef RUNTIME_DEBUG + int ok = ((beginning - base) < len) && ((beginning + size - base) < len) && + ((beginning - base) < max) && ((beginning + size - base) < max); + printf("bufGuard: beginning %ld end %ld: maxlen: %d size %d (%ld %ld %d): %s\n", + beginning - base, beginning + size - base, len, size, base, + beginning, size, ok ? "OK" : "!"); +#endif + return( + ((beginning - base) < len) && ((beginning + size - base) < len) && + ((beginning - base) < max) && ((beginning + size - base) < max) + ); +} + +void +unpackUInteger48( void *buf, void *i, PtpClock *ptpClock) +{ + + unpackUInteger16(buf, &((UInteger48*)i)->msb, ptpClock); + unpackUInteger32(buf + 2, &((UInteger48*)i)->lsb, ptpClock); +} + +void +packUInteger48( void *i, void *buf) +{ + packUInteger16(&((UInteger48*)i)->msb, buf); + packUInteger32(&((UInteger48*)i)->lsb, buf + 2); +} + +void +unpackInteger64( void *buf, void *i, PtpClock *ptpClock) +{ + unpackInteger32(buf, &((Integer64*)i)->msb, ptpClock); + unpackUInteger32(buf + 4, &((Integer64*)i)->lsb, ptpClock); +} + +void +packInteger64( void* i, void *buf ) +{ + packInteger32(&((Integer64*)i)->msb, buf); + packUInteger32(&((Integer64*)i)->lsb, buf + 4); +} + +/* NOTE: the unpack functions for management messages can probably be refactored into a macro */ +int +unpackMMSlaveOnly( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMSlaveOnly)); + MMSlaveOnly* data = (MMSlaveOnly*)m->tlv->dataField; + /* see src/def/README for a note on this X-macro */ + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/slaveOnly.def" + + #ifdef PTPD_DBG + mMSlaveOnly_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +/* NOTE: the pack functions for management messsages can probably be refactored into a macro */ +UInteger16 +packMMSlaveOnly( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMSlaveOnly* data = (MMSlaveOnly*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/slaveOnly.def" + + /* return length */ + return offset; +} + +int +unpackMMClockDescription( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMClockDescription)); + MMClockDescription* data = (MMClockDescription*)m->tlv->dataField; + memset(data, 0, sizeof(MMClockDescription)); + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/clockDescription.def" + + #ifdef PTPD_DBG + mMClockDescription_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMClockDescription( MsgManagement* m, Octet *buf) +{ + int offset = 0; + Octet pad = 0; + MMClockDescription* data = (MMClockDescription*)m->tlv->dataField; + data->reserved = 0; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset); \ + offset = offset + size; + #include "../def/managementTLV/clockDescription.def" + + /* is the TLV length odd? TLV must be even according to Spec 5.3.8 */ + if(offset % 2) { + /* add pad of 1 according to Table 41 to make TLV length even */ + packOctet(&pad, buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset); + offset = offset + 1; + } + + /* return length */ + return offset; +} + +void +freeMMClockDescription( MMClockDescription* data) +{ + #define OPERATE( name, size, type ) \ + free##type( &data->name); + #include "../def/managementTLV/clockDescription.def" +} + +int +unpackMMUserDescription( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMUserDescription)); + MMUserDescription* data = (MMUserDescription*)m->tlv->dataField; + memset(data, 0, sizeof(MMUserDescription)); + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/userDescription.def" + + #ifdef PTPD_DBG + /* mMUserDescription_display(data, ptpClock); */ + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMUserDescription( MsgManagement* m, Octet *buf) +{ + int offset = 0; + Octet pad = 0; + MMUserDescription* data = (MMUserDescription*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset); \ + offset = offset + size; + #include "../def/managementTLV/userDescription.def" + + /* is the TLV length odd? TLV must be even according to Spec 5.3.8 */ + if(offset % 2) { + /* add pad of 1 according to Table 41 to make TLV length even */ + packOctet(&pad, buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset); + offset = offset + 1; + } + + /* return length */ + return offset; +} + +void +freeMMUserDescription( MMUserDescription* data) +{ + #define OPERATE( name, size, type ) \ + free##type( &data->name); + #include "../def/managementTLV/userDescription.def" +} + +int unpackMMInitialize( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMInitialize)); + MMInitialize* data = (MMInitialize*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/initialize.def" + + #ifdef PTPD_DBG + mMInitialize_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMInitialize( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMInitialize* data = (MMInitialize*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/initialize.def" + + /* return length*/ + return offset; +} + +int unpackMMDefaultDataSet( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMDefaultDataSet)); + MMDefaultDataSet* data = (MMDefaultDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/defaultDataSet.def" + + #ifdef PTPD_DBG + mMDefaultDataSet_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMDefaultDataSet( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMDefaultDataSet* data = (MMDefaultDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/defaultDataSet.def" + + /* return length*/ + return offset; +} + +int unpackMMCurrentDataSet( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMCurrentDataSet)); + MMCurrentDataSet* data = (MMCurrentDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/currentDataSet.def" + + #ifdef PTPD_DBG + mMCurrentDataSet_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMCurrentDataSet( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMCurrentDataSet* data = (MMCurrentDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/currentDataSet.def" + + /* return length*/ + return offset; +} + +int unpackMMParentDataSet( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMParentDataSet)); + MMParentDataSet* data = (MMParentDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/parentDataSet.def" + + #ifdef PTPD_DBG + mMParentDataSet_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMParentDataSet( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMParentDataSet* data = (MMParentDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/parentDataSet.def" + + /* return length*/ + return offset; +} + +int unpackMMTimePropertiesDataSet( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMTimePropertiesDataSet)); + MMTimePropertiesDataSet* data = (MMTimePropertiesDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/timePropertiesDataSet.def" + + #ifdef PTPD_DBG + mMTimePropertiesDataSet_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMTimePropertiesDataSet( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMTimePropertiesDataSet* data = (MMTimePropertiesDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/timePropertiesDataSet.def" + + /* return length*/ + return offset; +} + +int unpackMMPortDataSet( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMPortDataSet)); + MMPortDataSet* data = (MMPortDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/portDataSet.def" + + #ifdef PTPD_DBG + mMPortDataSet_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMPortDataSet( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMPortDataSet* data = (MMPortDataSet*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/portDataSet.def" + + /* return length*/ + return offset; +} + +int unpackMMPriority1( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMPriority1)); + MMPriority1* data = (MMPriority1*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/priority1.def" + + #ifdef PTPD_DBG + mMPriority1_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMPriority1( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMPriority1* data = (MMPriority1*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/priority1.def" + + /* return length*/ + return offset; +} + +int unpackMMPriority2( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMPriority2)); + MMPriority2* data = (MMPriority2*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/priority2.def" + + #ifdef PTPD_DBG + mMPriority2_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMPriority2( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMPriority2* data = (MMPriority2*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/priority2.def" + + /* return length*/ + return offset; +} + +int unpackMMDomain( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMDomain)); + MMDomain* data = (MMDomain*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/domain.def" + + #ifdef PTPD_DBG + mMDomain_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMDomain( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMDomain* data = (MMDomain*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/domain.def" + + /* return length*/ + return offset; +} + +int unpackMMLogAnnounceInterval( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMLogAnnounceInterval)); + MMLogAnnounceInterval* data = (MMLogAnnounceInterval*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/logAnnounceInterval.def" + + #ifdef PTPD_DBG + mMLogAnnounceInterval_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMLogAnnounceInterval( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMLogAnnounceInterval* data = (MMLogAnnounceInterval*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/logAnnounceInterval.def" + + /* return length*/ + return offset; +} + +int unpackMMAnnounceReceiptTimeout( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField,sizeof(MMAnnounceReceiptTimeout)); + MMAnnounceReceiptTimeout* data = (MMAnnounceReceiptTimeout*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/announceReceiptTimeout.def" + + #ifdef PTPD_DBG + mMAnnounceReceiptTimeout_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMAnnounceReceiptTimeout( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMAnnounceReceiptTimeout* data = (MMAnnounceReceiptTimeout*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/announceReceiptTimeout.def" + + /* return length*/ + return offset; +} + +int unpackMMLogSyncInterval( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMLogSyncInterval)); + MMLogSyncInterval* data = (MMLogSyncInterval*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/logSyncInterval.def" + + #ifdef PTPD_DBG + mMLogSyncInterval_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMLogSyncInterval( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMLogSyncInterval* data = (MMLogSyncInterval*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/logSyncInterval.def" + + /* return length*/ + return offset; +} + +int unpackMMVersionNumber( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMVersionNumber)); + MMVersionNumber* data = (MMVersionNumber*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/versionNumber.def" + + #ifdef PTPD_DBG + mMVersionNumber_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMVersionNumber( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMVersionNumber* data = (MMVersionNumber*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/versionNumber.def" + + /* return length*/ + return offset; +} + +int unpackMMTime( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMTime)); + MMTime* data = (MMTime*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/time.def" + + #ifdef PTPD_DBG + mMTime_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMTime( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMTime* data = (MMTime*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/time.def" + + /* return length*/ + return offset; +} + +int unpackMMClockAccuracy( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMClockAccuracy)); + MMClockAccuracy* data = (MMClockAccuracy*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/clockAccuracy.def" + + #ifdef PTPD_DBG + mMClockAccuracy_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMClockAccuracy( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMClockAccuracy* data = (MMClockAccuracy*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/clockAccuracy.def" + + /* return length*/ + return offset; +} + +int unpackMMUtcProperties( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMUtcProperties)); + MMUtcProperties* data = (MMUtcProperties*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/utcProperties.def" + + #ifdef PTPD_DBG + mMUtcProperties_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMUtcProperties( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMUtcProperties* data = (MMUtcProperties*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/utcProperties.def" + + /* return length*/ + return offset; +} + +int unpackMMTraceabilityProperties( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMTraceabilityProperties)); + MMTraceabilityProperties* data = (MMTraceabilityProperties*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/traceabilityProperties.def" + + #ifdef PTPD_DBG + mMTraceabilityProperties_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMTraceabilityProperties( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMTraceabilityProperties* data = (MMTraceabilityProperties*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/traceabilityProperties.def" + + /* return length*/ + return offset; +} + +int unpackMMTimescaleProperties( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMTimescaleProperties)); + MMTimescaleProperties* data = (MMTimescaleProperties*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/timescaleProperties.def" + data->ptp = (data->ptp & 0x08) >> 3; + #ifdef PTPD_DBG + mMTimescaleProperties_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMTimescaleProperties( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMTimescaleProperties* data = (MMTimescaleProperties*)m->tlv->dataField; + data->ptp = (data->ptp << 3) & 0x08; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/timescaleProperties.def" + + /* return length*/ + return offset; +} + +int unpackMMUnicastNegotiationEnable( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMUnicastNegotiationEnable)); + MMUnicastNegotiationEnable* data = (MMUnicastNegotiationEnable*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/unicastNegotiationEnable.def" + + #ifdef PTPD_DBG + mMUnicastNegotiationEnable_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMUnicastNegotiationEnable( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMUnicastNegotiationEnable* data = (MMUnicastNegotiationEnable*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/unicastNegotiationEnable.def" + + /* return length*/ + return offset; +} + + +int unpackMMDelayMechanism( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMDelayMechanism)); + MMDelayMechanism* data = (MMDelayMechanism*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/delayMechanism.def" + + #ifdef PTPD_DBG + mMDelayMechanism_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMDelayMechanism( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMDelayMechanism* data = (MMDelayMechanism*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/delayMechanism.def" + + /* return length*/ + return offset; +} + +int unpackMMLogMinPdelayReqInterval( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMLogMinPdelayReqInterval)); + MMLogMinPdelayReqInterval* data = (MMLogMinPdelayReqInterval*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/logMinPdelayReqInterval.def" + + #ifdef PTPD_DBG + mMLogMinPdelayReqInterval_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +UInteger16 +packMMLogMinPdelayReqInterval( MsgManagement* m, Octet *buf) +{ + int offset = 0; + MMLogMinPdelayReqInterval* data = (MMLogMinPdelayReqInterval*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/logMinPdelayReqInterval.def" + + /* return length*/ + return offset; +} + +int unpackMMErrorStatus( Octet *buf, int baseOffset, MsgManagement* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->dataField, sizeof(MMErrorStatus)); + MMErrorStatus* data = (MMErrorStatus*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + if(!bufGuard(PACKET_SIZE, (long)buf, m->header.messageLength, (long)(buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset), size)) return 0;\ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + TLV_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/errorStatus.def" + + #ifdef PTPD_DBG + mMErrorStatus_display(data, ptpClock); + #endif /* PTPD_DBG */ + + return 1; + +} + +void +freeMMErrorStatus( MMErrorStatus* data) +{ + #define OPERATE( name, size, type ) \ + free##type( &data->name); + #include "../def/managementTLV/errorStatus.def" +} + +UInteger16 +packMMErrorStatus( MsgManagement* m, Octet *buf) +{ + int offset = 0; + Octet pad = 0; + MMErrorStatus* data = (MMErrorStatus*)m->tlv->dataField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/errorStatus.def" + + /* is the TLV length odd? TLV must be even according to Spec 5.3.8 */ + if(offset % 2) { + /* add pad of 1 according to Table 41 to make TLV length even */ + packOctet(&pad, buf + MANAGEMENT_LENGTH + TLV_LENGTH + offset); + offset = offset + 1; + } + + /* return length*/ + return offset; +} + +void +unpackSMRequestUnicastTransmission( Octet *buf, MsgSignaling* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->valueField, sizeof(SMRequestUnicastTransmission)); + SMRequestUnicastTransmission* data = (SMRequestUnicastTransmission*)m->tlv->valueField; + /* see src/def/README for a note on this X-macro */ + #define OPERATE( name, size, type ) \ + unpack##type( buf + SIGNALING_LENGTH + TL_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/signalingTLV/requestUnicastTransmission.def" + + #ifdef PTPD_DBG + sMRequestUnicastTransmission_display(data, ptpClock); + #endif /* PTPD_DBG */ + + +} + +UInteger16 +packSMRequestUnicastTransmission( MsgSignaling* m, Octet *buf) +{ + int offset = 0; + SMRequestUnicastTransmission* data = (SMRequestUnicastTransmission*)m->tlv->valueField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + SIGNALING_LENGTH + TL_LENGTH + offset ); \ + offset = offset + size; + #include "../def/signalingTLV/requestUnicastTransmission.def" + + /* return length */ + return offset; +} + +void +unpackSMGrantUnicastTransmission( Octet *buf, MsgSignaling* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->valueField, sizeof(SMGrantUnicastTransmission)); + SMGrantUnicastTransmission* data = (SMGrantUnicastTransmission*)m->tlv->valueField; + + /* see src/def/README for a note on this X-macro */ + #define OPERATE( name, size, type ) \ + unpack##type( buf + SIGNALING_LENGTH + TL_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/signalingTLV/requestUnicastTransmission.def" + + #ifdef PTPD_DBG + sMGrantUnicastTransmission_display(data, ptpClock); + #endif /* PTPD_DBG */ +} + +UInteger16 +packSMGrantUnicastTransmission( MsgSignaling* m, Octet *buf) +{ + int offset = 0; + SMGrantUnicastTransmission* data = (SMGrantUnicastTransmission*)m->tlv->valueField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + SIGNALING_LENGTH + TL_LENGTH + offset ); \ + offset = offset + size; + #include "../def/signalingTLV/grantUnicastTransmission.def" + + /* return length */ + return offset; +} + +void +unpackSMCancelUnicastTransmission( Octet *buf, MsgSignaling* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->valueField, sizeof(SMCancelUnicastTransmission)); + SMCancelUnicastTransmission* data = (SMCancelUnicastTransmission*)m->tlv->valueField; + /* see src/def/README for a note on this X-macro */ + #define OPERATE( name, size, type ) \ + unpack##type( buf + SIGNALING_LENGTH + TL_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/signalingTLV/cancelUnicastTransmission.def" + + #ifdef PTPD_DBG + sMCancelUnicastTransmission_display(data, ptpClock); + #endif /* PTPD_DBG */ + +} + +UInteger16 +packSMCancelUnicastTransmission( MsgSignaling* m, Octet *buf) +{ + int offset = 0; + SMCancelUnicastTransmission* data = (SMCancelUnicastTransmission*)m->tlv->valueField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + SIGNALING_LENGTH + TL_LENGTH + offset ); \ + offset = offset + size; + #include "../def/signalingTLV/cancelUnicastTransmission.def" + + /* return length */ + return offset; +} + +void +unpackSMAcknowledgeCancelUnicastTransmission( Octet *buf, MsgSignaling* m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv->valueField, sizeof(SMAcknowledgeCancelUnicastTransmission)); + SMAcknowledgeCancelUnicastTransmission* data = (SMAcknowledgeCancelUnicastTransmission*)m->tlv->valueField; + /* see src/def/README for a note on this X-macro */ + #define OPERATE( name, size, type ) \ + unpack##type( buf + SIGNALING_LENGTH + TL_LENGTH + offset,\ + &data->name, ptpClock ); \ + offset = offset + size; + #include "../def/signalingTLV/acknowledgeCancelUnicastTransmission.def" + + #ifdef PTPD_DBG + sMAcknowledgeCancelUnicastTransmission_display(data, ptpClock); + #endif /* PTPD_DBG */ + + +} + +UInteger16 +packSMAcknowledgeCancelUnicastTransmission( MsgSignaling* m, Octet *buf) +{ + int offset = 0; + SMAcknowledgeCancelUnicastTransmission* data = (SMAcknowledgeCancelUnicastTransmission*)m->tlv->valueField; + #define OPERATE( name, size, type ) \ + pack##type( &data->name,\ + buf + SIGNALING_LENGTH + TL_LENGTH + offset ); \ + offset = offset + size; + #include "../def/signalingTLV/acknowledgeCancelUnicastTransmission.def" + + /* return length */ + return offset; +} + +void +unpackClockIdentity( Octet *buf, ClockIdentity *c, PtpClock *ptpClock) +{ + int i; + for(i = 0; i < CLOCK_IDENTITY_LENGTH; i++) { + unpackOctet((buf+i),&((*c)[i]), ptpClock); + } +} + +void packClockIdentity( ClockIdentity *c, Octet *buf) +{ + int i; + for(i = 0; i < CLOCK_IDENTITY_LENGTH; i++) { + packOctet(&((*c)[i]),(buf+i)); + } +} + +void +freeClockIdentity( ClockIdentity *c) { + /* nothing to free */ +} + +void +unpackClockQuality( Octet *buf, ClockQuality *c, PtpClock *ptpClock) +{ + int offset = 0; + ClockQuality* data = c; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/derivedData/clockQuality.def" +} + +void +packClockQuality( ClockQuality *c, Octet *buf) +{ + int offset = 0; + ClockQuality *data = c; + #define OPERATE( name, size, type) \ + pack##type (&data->name, buf + offset); \ + offset = offset + size; + #include "../def/derivedData/clockQuality.def" +} + +void +freeClockQuality( ClockQuality *c) +{ + /* nothing to free */ +} + +void +unpackTimeInterval( Octet *buf, TimeInterval *t, PtpClock *ptpClock) +{ + int offset = 0; + TimeInterval* data = t; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/derivedData/timeInterval.def" +} + +void +packTimeInterval( TimeInterval *t, Octet *buf) +{ + int offset = 0; + TimeInterval *data = t; + #define OPERATE( name, size, type) \ + pack##type (&data->name, buf + offset); \ + offset = offset + size; + #include "../def/derivedData/timeInterval.def" +} + +void +freeTimeInterval( TimeInterval *t) +{ + /* nothing to free */ +} + +void +unpackTimestamp( Octet *buf, Timestamp *t, PtpClock *ptpClock) +{ + int offset = 0; + Timestamp* data = t; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/derivedData/timestamp.def" +} + +void +packTimestamp( Timestamp *t, Octet *buf) +{ + int offset = 0; + Timestamp *data = t; + #define OPERATE( name, size, type) \ + pack##type (&data->name, buf + offset); \ + offset = offset + size; + #include "../def/derivedData/timestamp.def" +} + +void +freeTimestamp( Timestamp *t) +{ + /* nothing to free */ +} + + +void +unpackPortIdentity( Octet *buf, PortIdentity *p, PtpClock *ptpClock) +{ + int offset = 0; + PortIdentity* data = p; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/derivedData/portIdentity.def" +} + +void +packPortIdentity( PortIdentity *p, Octet *buf) +{ + int offset = 0; + PortIdentity *data = p; + #define OPERATE( name, size, type) \ + pack##type (&data->name, buf + offset); \ + offset = offset + size; + #include "../def/derivedData/portIdentity.def" +} + +void +freePortIdentity( PortIdentity *p) +{ + /* nothing to free */ +} + +void +unpackPortAddress( Octet *buf, PortAddress *p, PtpClock *ptpClock) +{ + unpackEnumeration16( buf, &p->networkProtocol, ptpClock); + unpackUInteger16( buf+2, &p->addressLength, ptpClock); + if(p->addressLength) { + XMALLOC(p->addressField, p->addressLength); + memcpy( p->addressField, buf+4, p->addressLength); + } else { + p->addressField = NULL; + } +} + +void +packPortAddress(PortAddress *p, Octet *buf) +{ + packEnumeration16(&p->networkProtocol, buf); + packUInteger16(&p->addressLength, buf+2); + if(p->addressLength) { + memcpy( buf+4, p->addressField, p->addressLength); + } +} + +void +freePortAddress(PortAddress *p) +{ + if(p->addressField) { + free(p->addressField); + p->addressField = NULL; + } +} + +void +unpackPTPText( Octet *buf, PTPText *s, PtpClock *ptpClock) +{ + unpackUInteger8( buf, &s->lengthField, ptpClock); + if(s->lengthField) { + XMALLOC(s->textField, s->lengthField); + memcpy( s->textField, buf+1, s->lengthField); + } else { + s->textField = NULL; + } +} + +void +packPTPText(PTPText *s, Octet *buf) +{ + packUInteger8(&s->lengthField, buf); + if(s->lengthField) { + memcpy( buf+1, s->textField, s->lengthField); + } +} + +void +freePTPText(PTPText *s) +{ + if(s->textField) { + free(s->textField); + s->textField = NULL; + } +} + +void +unpackPhysicalAddress( Octet *buf, PhysicalAddress *p, PtpClock *ptpClock) +{ + unpackUInteger16( buf, &p->addressLength, ptpClock); + if(p->addressLength) { + XMALLOC(p->addressField, p->addressLength); + memcpy( p->addressField, buf+2, p->addressLength); + } else { + p->addressField = NULL; + } +} + +void +packPhysicalAddress(PhysicalAddress *p, Octet *buf) +{ + packUInteger16(&p->addressLength, buf); + if(p->addressLength) { + memcpy( buf+2, p->addressField, p->addressLength); + } +} + +void +freePhysicalAddress(PhysicalAddress *p) +{ + if(p->addressField) { + free(p->addressField); + p->addressField = NULL; + } +} + +void +copyClockIdentity( ClockIdentity dest, ClockIdentity src) +{ + memcpy(dest, src, CLOCK_IDENTITY_LENGTH); +} + +void +copyPortIdentity( PortIdentity *dest, PortIdentity *src) +{ + copyClockIdentity(dest->clockIdentity, src->clockIdentity); + dest->portNumber = src->portNumber; +} + +void +unpackMsgHeader(Octet *buf, MsgHeader *header, PtpClock *ptpClock) +{ + int offset = 0; + MsgHeader* data = header; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/message/header.def" +} + +void +packMsgHeader(MsgHeader *h, Octet *buf) +{ + int offset = 0; + + /* set uninitalized bytes to zero */ + h->reserved0 = 0; + h->reserved1 = 0; + h->reserved2 = 0; + + #define OPERATE( name, size, type ) \ + pack##type( &h->name, buf + offset ); \ + offset = offset + size; + #include "../def/message/header.def" +} + +void +unpackManagementTLV(Octet *buf, int baseOffset, MsgManagement *m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv, sizeof(ManagementTLV)); + /* read the management TLV */ + #define OPERATE( name, size, type ) \ + unpack##type( buf + baseOffset + MANAGEMENT_LENGTH + offset, &m->tlv->name, ptpClock ); \ + offset = offset + size; + #include "../def/managementTLV/managementTLV.def" +} + +void +packManagementTLV(ManagementTLV *tlv, Octet *buf) +{ + int offset = 0; + #define OPERATE( name, size, type ) \ + pack##type( &tlv->name, buf + MANAGEMENT_LENGTH + offset ); \ + offset = offset + size; + #include "../def/managementTLV/managementTLV.def" +} + +void +freeManagementTLV(MsgManagement *m) +{ + /* cleanup outgoing managementTLV */ + if(m->tlv) { + if(m->tlv->dataField) { + if(m->tlv->tlvType == TLV_MANAGEMENT) { + freeMMTLV(m->tlv); + } else if(m->tlv->tlvType == TLV_MANAGEMENT_ERROR_STATUS) { + freeMMErrorStatusTLV(m->tlv); + } + free(m->tlv->dataField); + m->tlv->dataField = NULL; + } + free(m->tlv); + m->tlv = NULL; + } +} + +void +packMsgManagement(MsgManagement *m, Octet *buf) +{ + int offset = 0; + MsgManagement *data = m; + + /* set unitialized bytes to zero */ + m->reserved0 = 0; + m->reserved1 = 0; + + #define OPERATE( name, size, type) \ + pack##type (&data->name, buf + offset); \ + offset = offset + size; + #include "../def/message/management.def" + +} + +void unpackMsgManagement(Octet *buf, MsgManagement *m, PtpClock *ptpClock) +{ + int offset = 0; + MsgManagement* data = m; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/message/management.def" + + #ifdef PTPD_DBG + msgManagement_display(data); + #endif /* PTPD_DBG */ +} + +void +unpackSignalingTLV(Octet *buf, MsgSignaling *m, PtpClock* ptpClock) +{ + int offset = 0; + XMALLOC(m->tlv, sizeof(SignalingTLV)); + /* read the signaling TLV */ + #define OPERATE( name, size, type ) \ + unpack##type( buf + SIGNALING_LENGTH + offset, &m->tlv->name, ptpClock ); \ + offset = offset + size; + #include "../def/signalingTLV/signalingTLV.def" +} + +void +packSignalingTLV(SignalingTLV *tlv, Octet *buf) +{ + int offset = 0; + #define OPERATE( name, size, type ) \ + pack##type( &tlv->name, buf + SIGNALING_LENGTH + offset ); \ + offset = offset + size; + #include "../def/signalingTLV/signalingTLV.def" +} + +void +freeSignalingTLV(MsgSignaling *m) +{ + /* cleanup outgoing signaling TLV */ + if(m->tlv) { + if(m->tlv->valueField) { + free(m->tlv->valueField); + m->tlv->valueField = NULL; + } + free(m->tlv); + m->tlv = NULL; + } +} + +void +packMsgSignaling(MsgSignaling *m, Octet *buf) +{ + int offset = 0; + MsgSignaling *data = m; + + #define OPERATE( name, size, type) \ + pack##type (&data->name, buf + offset); \ + offset = offset + size; + #include "../def/message/signaling.def" + +} + +void +unpackMsgSignaling(Octet *buf, MsgSignaling *m, PtpClock *ptpClock) +{ + int offset = 0; + MsgSignaling* data = m; + #define OPERATE( name, size, type) \ + unpack##type (buf + offset, &data->name, ptpClock); \ + offset = offset + size; + #include "../def/message/signaling.def" + + #ifdef PTPD_DBG + msgSignaling_display(data); + #endif /* PTPD_DBG */ +} + +/*Unpack Header from IN buffer to msgTmpHeader field */ +void +msgUnpackHeader(Octet * buf, MsgHeader * header) +{ + header->transportSpecific = (*(Nibble *) (buf + 0)) >> 4; + header->messageType = (*(Enumeration4 *) (buf + 0)) & 0x0F; + header->versionPTP = (*(UInteger4 *) (buf + 1)) & 0x0F; + /* force reserved bit to zero if not */ + header->messageLength = flip16(*(UInteger16 *) (buf + 2)); + header->domainNumber = (*(UInteger8 *) (buf + 4)); + header->flagField0 = (*(Octet *) (buf + 6)); + header->flagField1 = (*(Octet *) (buf + 7)); + memcpy(&header->correctionField.msb, (buf + 8), 4); + memcpy(&header->correctionField.lsb, (buf + 12), 4); + header->correctionField.msb = flip32(header->correctionField.msb); + header->correctionField.lsb = flip32(header->correctionField.lsb); + copyClockIdentity(header->sourcePortIdentity.clockIdentity, (buf + 20)); + header->sourcePortIdentity.portNumber = + flip16(*(UInteger16 *) (buf + 28)); + header->sequenceId = flip16(*(UInteger16 *) (buf + 30)); + header->controlField = (*(UInteger8 *) (buf + 32)); + header->logMessageInterval = (*(Integer8 *) (buf + 33)); + +#ifdef PTPD_DBG + msgHeader_display(header); +#endif /* PTPD_DBG */ +} + +/*Pack header message into OUT buffer of ptpClock*/ +// ##### SCORE MODIFICATION BEGIN ##### +void msgPackHeader(Octet *buf, PtpClock *ptpClock, MsgHeader *msgTmpHeader) { +// ##### SCORE MODIFICATION END ##### + /* (spec annex D) */ + *(UInteger8 *)(buf + 0) = ptpClock->portDS.transportSpecific << 4; + *(UInteger4 *)(buf + 1) = ptpClock->portDS.versionNumber; + *(UInteger8 *)(buf + 4) = ptpClock->defaultDS.domainNumber; + /* clear flag field - message packing functions should populate it */ + memset((buf + 6), 0, 2); + + memset((buf + 8), 0, 8); + copyClockIdentity((buf + 20), ptpClock->portDS.portIdentity.clockIdentity); + *(UInteger16 *)(buf + 28) = flip16(ptpClock->portDS.portIdentity.portNumber); + /* LogMessageInterval defaults to 0x7F, will be set to another value if needed as per table 24*/ + *(UInteger8 *)(buf + 33) = 0x7F; + +// ##### SCORE MODIFICATION BEGIN ##### + +#if SCORE_EXTENSIONS + if (NULL != msgTmpHeader) { + msgTmpHeader->transportSpecific = ptpClock->portDS.transportSpecific << 4; + msgTmpHeader->versionPTP = ptpClock->portDS.versionNumber & 0x0F; + msgTmpHeader->domainNumber = ptpClock->defaultDS.domainNumber; + copyClockIdentity(msgTmpHeader->sourcePortIdentity.clockIdentity, ptpClock->portDS.portIdentity.clockIdentity); + msgTmpHeader->sourcePortIdentity.portNumber = ptpClock->portDS.portIdentity.portNumber; + msgTmpHeader->logMessageInterval = 0x7F; + } +#endif + // ##### SCORE MODIFICATION END ##### +} + +#ifndef PTPD_SLAVE_ONLY +/*Pack SYNC message into OUT buffer of ptpClock*/ +void +msgPackSync(Octet * buf, UInteger16 sequenceId, Timestamp * originTimestamp, PtpClock * ptpClock) +{ +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x00; + /* Two step flag - table 20: Sync and PdelayResp only */ + if (ptpClock->defaultDS.twoStepFlag) + *(UInteger8 *) (buf + 6) |= PTP_TWO_STEP; + +// ##### SCORE MODIFICATION BEGIN ##### + +#if SCORE_EXTENSIONS +/** + * Ref: AUTOSAR_PRS_TimeSyncProtocol.pdf + * Octet 0: 0x02, + * Octet 1: 0x08 + * According to specification, this flag needs to be set to 0x08. + * Is it really needed?? Not sure yet.. + */ + *(UInteger8 *) (buf + 7) = 0x08; +#endif +// ##### SCORE MODIFICATION END ##### + + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(SYNC_LENGTH); + *(UInteger16 *) (buf + 30) = flip16(sequenceId); + *(UInteger8 *) (buf + 32) = 0x00; + + /* Table 24 - unless it's multicast, logMessageInterval remains 0x7F */ + if(rtOpts.transport == IEEE_802_3 || rtOpts.ipMode != IPMODE_UNICAST ) + *(Integer8 *) (buf + 33) = ptpClock->portDS.logSyncInterval; + memset((buf + 8), 0, 8); + +// ##### SCORE MODIFICATION BEGIN ##### + +#if SCORE_EXTENSIONS +/** + * Ref: AUTOSAR_PRS_TimeSyncProtocol.pdf + * Reserved field. 10 bytes from 34th index. To be filled with 0. + * Hence, update the values as below only for Non-S-CORE build. + */ + memset((buf + 34), 0, 10); + +#else + + /* Sync message */ + *(UInteger16 *) (buf + 34) = flip16(originTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = flip32(originTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = flip32(originTimestamp->nanosecondsField); + +#endif +// ##### SCORE MODIFICATION END ##### + +} +#endif /* PTPD_SLAVE_ONLY */ + +/*Unpack Sync message from IN buffer */ +void +msgUnpackSync(Octet * buf, MsgSync * sync) +{ + sync->originTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + sync->originTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + sync->originTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + +#ifdef PTPD_DBG + msgSync_display(sync); +#endif /* PTPD_DBG */ +} + + +/* When building slave only, this code does not get compiled */ +#ifndef PTPD_SLAVE_ONLY +/*Pack Announce message into OUT buffer of ptpClock*/ +void +msgPackAnnounce(Octet * buf, UInteger16 sequenceId, Timestamp * originTimestamp, PtpClock * ptpClock) +{ + UInteger16 stepsRemoved; +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x0B; + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(ANNOUNCE_LENGTH); + *(UInteger16 *) (buf + 30) = flip16(sequenceId); + *(UInteger8 *) (buf + 32) = 0x05; + /* Table 24: for Announce, logMessageInterval is never 0x7F */ + *(Integer8 *) (buf + 33) = ptpClock->portDS.logAnnounceInterval; + + /* Announce message */ + *(UInteger16 *) (buf + 34) = flip16(originTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = flip32(originTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = flip32(originTimestamp->nanosecondsField); + + *(Integer16 *) (buf + 44) = flip16(ptpClock->timePropertiesDS.currentUtcOffset); + *(UInteger8 *) (buf + 47) = ptpClock->parentDS.grandmasterPriority1; + *(UInteger8 *) (buf + 48) = ptpClock->defaultDS.clockQuality.clockClass; + *(Enumeration8 *) (buf + 49) = ptpClock->defaultDS.clockQuality.clockAccuracy; + *(UInteger16 *) (buf + 50) = + flip16(ptpClock->defaultDS.clockQuality.offsetScaledLogVariance); + *(UInteger8 *) (buf + 52) = ptpClock->parentDS.grandmasterPriority2; + copyClockIdentity((buf + 53), ptpClock->parentDS.grandmasterIdentity); + /* resolve bugs #37 and #40 - alignment errors on ARMv5 */ + stepsRemoved = flip16(ptpClock->currentDS.stepsRemoved); + memcpy(buf + 61, &stepsRemoved, sizeof(UInteger16)); + *(Enumeration8 *) (buf + 63) = ptpClock->timePropertiesDS.timeSource; + + /* + * TimePropertiesDS in FlagField, 2nd octet - spec 13.3.2.6 table 20 + * Could / should have used constants here PTP_LI_61 etc, but this is clean + */ + *(UInteger8*) (buf + 7) = ptpClock->timePropertiesDS.leap61 << 0; + *(UInteger8*) (buf + 7) |= (ptpClock->timePropertiesDS.leap59) << 1; + *(UInteger8*) (buf + 7) |= (ptpClock->timePropertiesDS.currentUtcOffsetValid) << 2; + *(UInteger8*) (buf + 7) |= (ptpClock->timePropertiesDS.ptpTimescale) << 3; + *(UInteger8*) (buf + 7) |= (ptpClock->timePropertiesDS.timeTraceable) << 4; + *(UInteger8*) (buf + 7) |= (ptpClock->timePropertiesDS.frequencyTraceable) << 5; +} +#endif /* PTPD_SLAVE_ONLY */ + +/*Unpack Announce message from IN buffer of ptpClock to msgtmp.Announce*/ +void +msgUnpackAnnounce(Octet * buf, MsgAnnounce * announce) +{ + UInteger16 stepsRemoved; + + announce->originTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + announce->originTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + announce->originTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + announce->currentUtcOffset = flip16(*(UInteger16 *) (buf + 44)); + announce->grandmasterPriority1 = *(UInteger8 *) (buf + 47); + announce->grandmasterClockQuality.clockClass = + *(UInteger8 *) (buf + 48); + announce->grandmasterClockQuality.clockAccuracy = + *(Enumeration8 *) (buf + 49); + announce->grandmasterClockQuality.offsetScaledLogVariance = + flip16(*(UInteger16 *) (buf + 50)); + announce->grandmasterPriority2 = *(UInteger8 *) (buf + 52); + copyClockIdentity(announce->grandmasterIdentity, (buf + 53)); + /* resolve bugs #37 and #40 - alignment errors on ARMv5 */ + memcpy(&stepsRemoved, buf + 61, sizeof(UInteger16)); + announce->stepsRemoved = flip16(stepsRemoved); + announce->timeSource = *(Enumeration8 *) (buf + 63); + + #ifdef PTPD_DBG + msgAnnounce_display(announce); + #endif /* PTPD_DBG */ +} + +#ifndef PTPD_SLAVE_ONLY /* does not get compiled when building slave only */ +/*pack Follow_up message into OUT buffer of ptpClock*/ +void +msgPackFollowUp(Octet * buf, Timestamp * preciseOriginTimestamp, PtpClock * ptpClock, const UInteger16 sequenceId) +{ +// ##### SCORE MODIFICATION BEGIN ##### + /* To create a means to share the followup header info with S-CORE extensions */ + MsgHeader msgTmpHeader; + memset(&msgTmpHeader,0u,sizeof(MsgHeader)); + + msgPackHeader(buf, ptpClock, NULL); + + /* changes in header */ + *(char *)(buf + SCORE_PTP_FUP_MSG_TYPE_IDX) = *(char *)(buf + SCORE_PTP_FUP_MSG_TYPE_IDX) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + SCORE_PTP_FUP_MSG_TYPE_IDX) = *(char *)(buf + SCORE_PTP_FUP_MSG_TYPE_IDX) | 0x08; + +#if SCORE_EXTENSIONS + msgTmpHeader.messageType = ((msgTmpHeader.messageType & 0xF0) | FOLLOW_UP); +/** + * Ref: AUTOSAR_PRS_TimeSyncProtocol.pdf + * Octet 0: 0x00, + * Octet 1: 0x08 + * According to specification, this flag needs to be set to 0x08. + * Is it really needed?? Not sure yet. + */ + *(UInteger8 *) (buf + SCORE_PTP_FUP_FLAGS_OCTET_0_IDX) = 0x00; + *(UInteger8 *) (buf + SCORE_PTP_FUP_FLAGS_OCTET_1_IDX) = 0x08; + + msgTmpHeader.flagField0 = 0x00; + msgTmpHeader.flagField1 = 0x08; +#endif + + /* Table 19 */ + *(UInteger16 *) (buf + SCORE_PTP_FUP_MSG_LENGTH_IDX) = flip16(ptpConfig.configGlobals.followupLength); + *(UInteger16 *) (buf + SCORE_PTP_FUP_SEQUENCE_ID_IDX) = flip16(sequenceId); + *(UInteger8 *) (buf + SCORE_PTP_FUP_CONTROL_IDX) = 0x02; + + msgTmpHeader.messageLength = ptpConfig.configGlobals.followupLength; + msgTmpHeader.sequenceId = sequenceId; + msgTmpHeader.controlField = 0x02; + + /* Table 24 - unless it's multicast, logMessageInterval remains 0x7F */ + if(rtOpts.transport == IEEE_802_3 || rtOpts.ipMode != IPMODE_UNICAST) + *(Integer8 *) (buf + SCORE_PTP_FUP_LOG_MSG_INTERVAL_IDX) = + ptpClock->portDS.logSyncInterval; + + msgTmpHeader.logMessageInterval = ptpClock->portDS.logSyncInterval; + + /* Follow_up message */ + *(UInteger16 *) (buf + SCORE_PTP_FUP_ORIGIN_TIME_SECONDSHI_IDX) = + flip16(preciseOriginTimestamp->secondsField.msb); + *(UInteger32 *) (buf + SCORE_PTP_FUP_ORIGIN_TIME_SECONDS_IDX) = + flip32(preciseOriginTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + SCORE_PTP_FUP_ORIGIN_TIME_NANOSECONDS_IDX) = + flip32(preciseOriginTimestamp->nanosecondsField); + + + /* Update Follow-up information TLV and Follow-up information TLV Autosar */ + /* 34 bytes - Follow-up Message header + 10 bytes - Precise-originTimeStamp + 32 bytes - Follow-up information TLV (TOTAL = 76 bytes for IEEE 802.1AS) + + Following bytes are Additional fields in case of Autosar + + 10 bytes - Follow-up information TLV (AUTOSAR) + 37 bytes - SubTLV (TOTAL = 76 + 10 + 37 )) */ + + +#if SCORE_EXTENSIONS + Score_PtpExtractFupHeaderInfo( &msgTmpHeader, SCORE_PTP_TRUE); + + /* Update the fup info and autosar tlvs + The second parameter passes the remaining available followup buffer + capacity. The called function can then verify if this capacity is + sufficient to update complete follow-up information */ + Score_PtpWriteFupInfoToBuffer( (uint8_t*) (buf + SCORE_PTP_FUP_INFO_START_IDX), + SCORE_PTP_FUP_INFO_TLV_LENGTH); + +#endif +// ##### SCORE MODIFICATION END ##### +} +#endif /* PTPD_SLAVE_ONLY */ + + +/** @brief Update the FUP information for IEEE 802.1AS and AUTOSAR + * @details + * This function is used by Provider to update all FUP information related to + * both IEEE 802.1AS and AUTOSAR. + * @param[out] fupInfoBufPtrU8 Pointer to buffer where FUP information shall be + * updated by this function. + * @param[in] bufSizeU8 Size of the buffer being passed. + * @return None. + */ +static void Score_PtpWriteFupInfoToBuffer(uint8_t *fupInfoBufPtrU8, uint8_t bufSizeU8) +{ + /** Follow_Up information TLV [IEEE 802.1AS] + * 34 bytes - Follow-up Message header (Already ptpd has the implementation) + * 10 bytes - Precise-originTimeStamp (Already ptpd has the implementation) + * + * Following fields shall be populated in this function. + * 32 bytes - Follow-up information TLV (TOTAL = 76 bytes for IEEE 802.1AS + * Following bytes are Additional fields in case of Autosar + * 10 bytes - Follow-up information TLV (AUTOSAR) + * 37 bytes - SubTLV (TOTAL = 76 + 10 + 37 )) + */ + if ( (NULL != fupInfoBufPtrU8) && (SCORE_PTP_FUP_INFO_TLV_LENGTH == bufSizeU8) ) + { + Score_PtpProtocolFupInfoType *fupinfo =(Score_PtpProtocolFupInfoType *) fupInfoBufPtrU8; + + /* [PRS_TS_00181] The byte order for multibyte values is Big Endian */ + fupinfo->tlvType = flip16(SCORE_PTP_FUP_TLV_TYPE); + fupinfo->tlvLength = flip16(SCORE_PTP_FUP_TLV_LENGTH); + + /* OrganizationId 0x0080C2 [IEEE802.1AS] */ + fupinfo->orgId[0] = (SCORE_PTP_FUP_TLV_ORGAID >> 16) & 0xFF; + fupinfo->orgId[1] = (SCORE_PTP_FUP_TLV_ORGAID >> 8 ) & 0xFF; + fupinfo->orgId[2] = SCORE_PTP_FUP_TLV_ORGAID & 0xFF; + + /* organizationSub-Type */ + fupinfo->orgSubType[0] = (SCORE_PTP_FUP_TLV_ORGASUBTYPE >> 16) & 0xFF; + fupinfo->orgSubType[1] = (SCORE_PTP_FUP_TLV_ORGASUBTYPE >> 8 ) & 0xFF; + fupinfo->orgSubType[2] = SCORE_PTP_FUP_TLV_ORGASUBTYPE & 0xFF; + + fupinfo->scaledrateOffset = flip32(SCORE_PTP_FUP_TLV_SCALEDRATEOFF); + + /* GmTimeBaseIndicator */ + fupinfo->gmTimeBaseInd = flip16(SCORE_PTP_FUP_TLV_GMTIMEBASEIND); + + /* LastGmPhaseChange */ + memset(fupinfo->lastGmPhaseChange, SCORE_PTP_FUP_TLV_LASTGMPHASECHG, sizeof(fupinfo->lastGmPhaseChange)); + + /* ScaledLastGm-FreqChange */ + fupinfo->lastGmFreqChange = flip32(SCORE_PTP_FUP_TLV_LASTGMFREQCHG); + + /* Follow_Up information TLV [AUTOSAR] */ + + /* Update AUTOSAR TLV and Sub-TLVs */ + Score_PtpWriteAutosarTlvToBuffer(&fupinfo->autosarTlv[0]); + + } +} + + +/*Unpack Follow_up message from IN buffer of ptpClock to msgtmp.follow*/ +void +msgUnpackFollowUp(Octet * buf, MsgFollowUp * follow) +{ + follow->preciseOriginTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + SCORE_PTP_FUP_ORIGIN_TIME_SECONDSHI_IDX)); + follow->preciseOriginTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + SCORE_PTP_FUP_ORIGIN_TIME_SECONDS_IDX)); + follow->preciseOriginTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + SCORE_PTP_FUP_ORIGIN_TIME_NANOSECONDS_IDX)); + + #ifdef PTPD_DBG + msgFollowUp_display(follow); + #endif /* PTPD_DBG */ +} + + +/*pack PdelayReq message into OUT buffer of ptpClock*/ +void +msgPackPdelayReq(Octet * buf, Timestamp * originTimestamp, PtpClock * ptpClock) +{ +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x02; + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(PDELAY_REQ_LENGTH); + *(UInteger16 *) (buf + 30) = flip16(ptpClock->sentPdelayReqSequenceId); + *(UInteger8 *) (buf + 32) = 0x05; + /* Table 23 */ + *(Integer8 *) (buf + 33) = 0x7F; + /* Table 24 */ + memset((buf + 8), 0, 8); + + /* Pdelay_req message */ + *(UInteger16 *) (buf + 34) = flip16(originTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = flip32(originTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = flip32(originTimestamp->nanosecondsField); + + memset((buf + 44), 0, 10); + /* RAZ reserved octets */ +} + +/*pack delayReq message into OUT buffer of ptpClock*/ +void +msgPackDelayReq(Octet * buf, Timestamp * originTimestamp, PtpClock * ptpClock) +{ +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x01; + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(DELAY_REQ_LENGTH); + + /* -- PTP_UNICAST flag will be set in netsend* if needed */ + + *(UInteger16 *) (buf + 30) = flip16(ptpClock->sentDelayReqSequenceId); + *(UInteger8 *) (buf + 32) = 0x01; + /* Table 23 */ + *(Integer8 *) (buf + 33) = 0x7F; + /* Table 24 */ + memset((buf + 8), 0, 8); + + /* Pdelay_req message */ + *(UInteger16 *) (buf + 34) = flip16(originTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = flip32(originTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = flip32(originTimestamp->nanosecondsField); +} + +/*pack delayResp message into OUT buffer of ptpClock*/ +void +msgPackDelayResp(Octet * buf, MsgHeader * header, Timestamp * receiveTimestamp, PtpClock * ptpClock) +{ +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x09; + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(DELAY_RESP_LENGTH); + *(UInteger8 *) (buf + 4) = header->domainNumber; + + /* -- PTP_UNICAST flag will be set in netsend* if needed */ + + memset((buf + 8), 0, 8); + + /* Copy correctionField of PdelayReqMessage */ + *(Integer32 *) (buf + 8) = flip32(header->correctionField.msb); + *(Integer32 *) (buf + 12) = flip32(header->correctionField.lsb); + + *(UInteger16 *) (buf + 30) = flip16(header->sequenceId); + + *(UInteger8 *) (buf + 32) = 0x03; + + /* Table 24 - unless it's multicast, logMessageInterval remains 0x7F */ + /* really tempting to cheat here, at least for hybrid, but standard is a standard */ + if ((header->flagField0 & PTP_UNICAST) != PTP_UNICAST) { + *(Integer8 *) (buf + 33) = ptpClock->portDS.logMinDelayReqInterval; + } + + /* Pdelay_resp message */ + *(UInteger16 *) (buf + 34) = + flip16(receiveTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = flip32(receiveTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = flip32(receiveTimestamp->nanosecondsField); + copyClockIdentity((buf + 44), header->sourcePortIdentity.clockIdentity); + *(UInteger16 *) (buf + 52) = + flip16(header->sourcePortIdentity.portNumber); +} + +/*pack PdelayResp message into OUT buffer of ptpClock*/ +void +msgPackPdelayResp(Octet * buf, MsgHeader * header, Timestamp * requestReceiptTimestamp, PtpClock * ptpClock) +{ +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x03; + /* Two step flag - table 20: Sync and PdelayResp only */ + if (ptpClock->defaultDS.twoStepFlag) + *(UInteger8 *) (buf + 6) |= PTP_TWO_STEP; + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(PDELAY_RESP_LENGTH); + *(UInteger8 *) (buf + 4) = header->domainNumber; + memset((buf + 8), 0, 8); + + + *(UInteger16 *) (buf + 30) = flip16(header->sequenceId); + + *(UInteger8 *) (buf + 32) = 0x05; + /* Table 23 */ + *(Integer8 *) (buf + 33) = 0x7F; + /* Table 24 */ + + /* Pdelay_resp message */ + *(UInteger16 *) (buf + 34) = flip16(requestReceiptTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = flip32(requestReceiptTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = flip32(requestReceiptTimestamp->nanosecondsField); + copyClockIdentity((buf + 44), header->sourcePortIdentity.clockIdentity); + *(UInteger16 *) (buf + 52) = flip16(header->sourcePortIdentity.portNumber); + +} + + +/*Unpack delayReq message from IN buffer of ptpClock to msgtmp.req*/ +void +msgUnpackDelayReq(Octet * buf, MsgDelayReq * delayreq) +{ + delayreq->originTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + delayreq->originTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + delayreq->originTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + + #ifdef PTPD_DBG + msgDelayReq_display(delayreq); + #endif /* PTPD_DBG */ + +} + + +/*Unpack PdelayReq message from IN buffer of ptpClock to msgtmp.req*/ +void +msgUnpackPdelayReq(Octet * buf, MsgPdelayReq * pdelayreq) +{ + pdelayreq->originTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + pdelayreq->originTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + pdelayreq->originTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + + #ifdef PTPD_DBG + msgPdelayReq_display(pdelayreq); + #endif /* PTPD_DBG */ + +} + + +/*Unpack delayResp message from IN buffer of ptpClock to msgtmp.presp*/ +void +msgUnpackDelayResp(Octet * buf, MsgDelayResp * resp) +{ + resp->receiveTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + resp->receiveTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + resp->receiveTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + copyClockIdentity(resp->requestingPortIdentity.clockIdentity, + (buf + 44)); + resp->requestingPortIdentity.portNumber = + flip16(*(UInteger16 *) (buf + 52)); + + #ifdef PTPD_DBG + msgDelayResp_display(resp); + #endif /* PTPD_DBG */ +} + + +/*Unpack PdelayResp message from IN buffer of ptpClock to msgtmp.presp*/ +void +msgUnpackPdelayResp(Octet * buf, MsgPdelayResp * presp) +{ + presp->requestReceiptTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + presp->requestReceiptTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + presp->requestReceiptTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + copyClockIdentity(presp->requestingPortIdentity.clockIdentity, + (buf + 44)); + presp->requestingPortIdentity.portNumber = + flip16(*(UInteger16 *) (buf + 52)); + + #ifdef PTPD_DBG + msgPdelayResp_display(presp); + #endif /* PTPD_DBG */ +} + +/*pack PdelayRespfollowup message into OUT buffer of ptpClock*/ +void +msgPackPdelayRespFollowUp(Octet * buf, MsgHeader * header, Timestamp * responseOriginTimestamp, PtpClock * ptpClock, const UInteger16 sequenceId) +{ +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(buf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + + /* changes in header */ + *(char *)(buf + 0) = *(char *)(buf + 0) & 0xF0; + /* RAZ messageType */ + *(char *)(buf + 0) = *(char *)(buf + 0) | 0x0A; + /* Table 19 */ + *(UInteger16 *) (buf + 2) = flip16(PDELAY_RESP_FOLLOW_UP_LENGTH); + *(UInteger16 *) (buf + 30) = flip16(sequenceId); + *(UInteger8 *) (buf + 32) = 0x05; + /* Table 23 */ + *(Integer8 *) (buf + 33) = 0x7F; + /* Table 24 */ + + /* Copy correctionField of PdelayReqMessage */ + *(Integer32 *) (buf + 8) = flip32(header->correctionField.msb); + *(Integer32 *) (buf + 12) = flip32(header->correctionField.lsb); + + /* Pdelay_resp_follow_up message */ + *(UInteger16 *) (buf + 34) = + flip16(responseOriginTimestamp->secondsField.msb); + *(UInteger32 *) (buf + 36) = + flip32(responseOriginTimestamp->secondsField.lsb); + *(UInteger32 *) (buf + 40) = + flip32(responseOriginTimestamp->nanosecondsField); + copyClockIdentity((buf + 44), header->sourcePortIdentity.clockIdentity); + *(UInteger16 *) (buf + 52) = + flip16(header->sourcePortIdentity.portNumber); +} + +/*Unpack PdelayResp message from IN buffer of ptpClock to msgtmp.presp*/ +void +msgUnpackPdelayRespFollowUp(Octet * buf, MsgPdelayRespFollowUp * prespfollow) +{ + prespfollow->responseOriginTimestamp.secondsField.msb = + flip16(*(UInteger16 *) (buf + 34)); + prespfollow->responseOriginTimestamp.secondsField.lsb = + flip32(*(UInteger32 *) (buf + 36)); + prespfollow->responseOriginTimestamp.nanosecondsField = + flip32(*(UInteger32 *) (buf + 40)); + copyClockIdentity(prespfollow->requestingPortIdentity.clockIdentity, + (buf + 44)); + prespfollow->requestingPortIdentity.portNumber = + flip16(*(UInteger16 *) (buf + 52)); + +#ifdef PTPD_DBG + msgPdelayRespFollowUp_display(prespfollow); +#endif /* PTPD_DBG */ +} + +/* Pack Management message into OUT buffer */ +void +msgPackManagementTLV(Octet *buf, MsgManagement *outgoing, PtpClock *ptpClock) +{ + DBGV("packing ManagementTLV message \n"); + + UInteger16 dataLength = 0; + + switch(outgoing->tlv->managementId) + { + case MM_NULL_MANAGEMENT: + case MM_SAVE_IN_NON_VOLATILE_STORAGE: + case MM_RESET_NON_VOLATILE_STORAGE: + case MM_ENABLE_PORT: + case MM_DISABLE_PORT: + dataLength = 0; + break; + case MM_CLOCK_DESCRIPTION: + dataLength = packMMClockDescription(outgoing, buf); + #ifdef PTPD_DBG + mMClockDescription_display( + (MMClockDescription*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_USER_DESCRIPTION: + dataLength = packMMUserDescription(outgoing, buf); + #ifdef PTPD_DBG + mMUserDescription_display( + (MMUserDescription*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_INITIALIZE: + dataLength = packMMInitialize(outgoing, buf); + #ifdef PTPD_DBG + mMInitialize_display( + (MMInitialize*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_DEFAULT_DATA_SET: + dataLength = packMMDefaultDataSet(outgoing, buf); + #ifdef PTPD_DBG + mMDefaultDataSet_display( + (MMDefaultDataSet*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_CURRENT_DATA_SET: + dataLength = packMMCurrentDataSet(outgoing, buf); + #ifdef PTPD_DBG + mMCurrentDataSet_display( + (MMCurrentDataSet*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_PARENT_DATA_SET: + dataLength = packMMParentDataSet(outgoing, buf); + #ifdef PTPD_DBG + mMParentDataSet_display( + (MMParentDataSet*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_TIME_PROPERTIES_DATA_SET: + dataLength = packMMTimePropertiesDataSet(outgoing, buf); + #ifdef PTPD_DBG + mMTimePropertiesDataSet_display( + (MMTimePropertiesDataSet*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_PORT_DATA_SET: + dataLength = packMMPortDataSet(outgoing, buf); + #ifdef PTPD_DBG + mMPortDataSet_display( + (MMPortDataSet*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_PRIORITY1: + dataLength = packMMPriority1(outgoing, buf); + #ifdef PTPD_DBG + mMPriority1_display( + (MMPriority1*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_PRIORITY2: + dataLength = packMMPriority2(outgoing, buf); + #ifdef PTPD_DBG + mMPriority2_display( + (MMPriority2*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_DOMAIN: + dataLength = packMMDomain(outgoing, buf); + #ifdef PTPD_DBG + mMDomain_display( + (MMDomain*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_SLAVE_ONLY: + dataLength = packMMSlaveOnly(outgoing, buf); + #ifdef PTPD_DBG + mMSlaveOnly_display( + (MMSlaveOnly*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_LOG_ANNOUNCE_INTERVAL: + dataLength = packMMLogAnnounceInterval(outgoing, buf); + #ifdef PTPD_DBG + mMLogAnnounceInterval_display( + (MMLogAnnounceInterval*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_ANNOUNCE_RECEIPT_TIMEOUT: + dataLength = packMMAnnounceReceiptTimeout(outgoing, buf); + #ifdef PTPD_DBG + mMAnnounceReceiptTimeout_display( + (MMAnnounceReceiptTimeout*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_LOG_SYNC_INTERVAL: + dataLength = packMMLogSyncInterval(outgoing, buf); + #ifdef PTPD_DBG + mMLogSyncInterval_display( + (MMLogSyncInterval*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_VERSION_NUMBER: + dataLength = packMMVersionNumber(outgoing, buf); + #ifdef PTPD_DBG + mMVersionNumber_display( + (MMVersionNumber*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_TIME: + dataLength = packMMTime(outgoing, buf); + #ifdef PTPD_DBG + mMTime_display( + (MMTime*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_CLOCK_ACCURACY: + dataLength = packMMClockAccuracy(outgoing, buf); + #ifdef PTPD_DBG + mMClockAccuracy_display( + (MMClockAccuracy*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_UTC_PROPERTIES: + dataLength = packMMUtcProperties(outgoing, buf); + #ifdef PTPD_DBG + mMUtcProperties_display( + (MMUtcProperties*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_TRACEABILITY_PROPERTIES: + dataLength = packMMTraceabilityProperties(outgoing, buf); + #ifdef PTPD_DBG + mMTraceabilityProperties_display( + (MMTraceabilityProperties*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_UNICAST_NEGOTIATION_ENABLE: + dataLength = packMMUnicastNegotiationEnable(outgoing, buf); + #ifdef PTPD_DBG + mMUnicastNegotiationEnable_display( + (MMUnicastNegotiationEnable*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_DELAY_MECHANISM: + dataLength = packMMDelayMechanism(outgoing, buf); + #ifdef PTPD_DBG + mMDelayMechanism_display( + (MMDelayMechanism*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + case MM_LOG_MIN_PDELAY_REQ_INTERVAL: + dataLength = packMMLogMinPdelayReqInterval(outgoing, buf); + #ifdef PTPD_DBG + mMLogMinPdelayReqInterval_display( + (MMLogMinPdelayReqInterval*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + break; + default: + DBGV("packing management msg: unsupported id \n"); + } + + /* set the outgoing tlv lengthField to 2 + N where 2 is the managementId field + * and N is dataLength, the length of the management tlv dataField field. + * See Table 39 of the spec. + */ + outgoing->tlv->lengthField = ((uint16_t)2u) + dataLength; + + packManagementTLV((ManagementTLV*)outgoing->tlv, buf); +} + +/* Pack ManagementErrorStatusTLV message into OUT buffer */ +void +msgPackManagementErrorStatusTLV(Octet *buf, MsgManagement *outgoing, + PtpClock *ptpClock) +{ + DBGV("packing ManagementErrorStatusTLV message \n"); + + UInteger16 dataLength = 0; + + dataLength = packMMErrorStatus(outgoing, buf); + #ifdef PTPD_DBG + mMErrorStatus_display((MMErrorStatus*)outgoing->tlv->dataField, ptpClock); + #endif /* PTPD_DBG */ + + /* set the outgoing tlv lengthField to 2 + (6 + N) where 2 is the + * managementErrorId field and (6 + N) is dataLength, where 6 is + * the managementId and reserved field and N is the displayData field + * and optional pad field. See Table 71 of the spec. + */ + outgoing->tlv->lengthField = ((uint16_t)2u) + dataLength; + + packManagementTLV((ManagementTLV*)outgoing->tlv, buf); +} + +/* Pack singaling message into OUT buffer */ +void +msgPackSignalingTLV(Octet *buf, MsgSignaling *outgoing, PtpClock * ptpClock) +{ + DBGV("packing SignalingTLV message \n"); + + (void)ptpClock; + + UInteger16 dataLength = 0; + + switch(outgoing->tlv->tlvType) + { + case TLV_REQUEST_UNICAST_TRANSMISSION: + dataLength = packSMRequestUnicastTransmission(outgoing, buf); + break; + case TLV_GRANT_UNICAST_TRANSMISSION: + dataLength = packSMGrantUnicastTransmission(outgoing, buf); + break; + case TLV_CANCEL_UNICAST_TRANSMISSION: + dataLength = packSMCancelUnicastTransmission(outgoing, buf); + break; + case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: + dataLength = packSMAcknowledgeCancelUnicastTransmission(outgoing, buf); + break; + default: + DBGV("packing signaling msg: unsupported tlv type \n"); + }; + /* set the outgoing tlv lengthField to 2 + N where 2 is the managementId field + * and N is dataLength, the length of the management tlv dataField field. + * See Table 39 of the spec. + */ + outgoing->tlv->lengthField = dataLength; + packSignalingTLV((SignalingTLV*)outgoing->tlv, buf); +} + +void +freeMMTLV(ManagementTLV* tlv) { + DBGV("cleanup managementTLV data\n"); + switch(tlv->managementId) + { + case MM_CLOCK_DESCRIPTION: + DBGV("cleanup clock description \n"); + freeMMClockDescription((MMClockDescription*)tlv->dataField); + break; + case MM_USER_DESCRIPTION: + DBGV("cleanup user description \n"); + freeMMUserDescription((MMUserDescription*)tlv->dataField); + break; + case MM_NULL_MANAGEMENT: + case MM_SAVE_IN_NON_VOLATILE_STORAGE: + case MM_RESET_NON_VOLATILE_STORAGE: + case MM_INITIALIZE: + case MM_DEFAULT_DATA_SET: + case MM_CURRENT_DATA_SET: + case MM_PARENT_DATA_SET: + case MM_TIME_PROPERTIES_DATA_SET: + case MM_PORT_DATA_SET: + case MM_PRIORITY1: + case MM_PRIORITY2: + case MM_DOMAIN: + case MM_SLAVE_ONLY: + case MM_LOG_ANNOUNCE_INTERVAL: + case MM_ANNOUNCE_RECEIPT_TIMEOUT: + case MM_LOG_SYNC_INTERVAL: + case MM_VERSION_NUMBER: + case MM_ENABLE_PORT: + case MM_DISABLE_PORT: + case MM_TIME: + case MM_CLOCK_ACCURACY: + case MM_UTC_PROPERTIES: + case MM_TRACEABILITY_PROPERTIES: + case MM_UNICAST_NEGOTIATION_ENABLE: + case MM_DELAY_MECHANISM: + case MM_LOG_MIN_PDELAY_REQ_INTERVAL: + default: + DBGV("no managementTLV data to cleanup \n"); + } +} + +void +freeMMErrorStatusTLV(ManagementTLV *tlv) { + DBGV("cleanup managementErrorStatusTLV data \n"); + freeMMErrorStatus((MMErrorStatus*)tlv->dataField); +} + +void +msgPackManagement(Octet *buf, MsgManagement *outgoing, PtpClock *ptpClock) +{ + DBGV("packing management message \n"); + (void)ptpClock; + + packMsgManagement(outgoing, buf); + +} + +/* + * Unpack Management message from IN buffer of ptpClock to msgtmp.manage + * return TRUE if there are more packed TLVs left in the message + */ +Boolean +msgUnpackManagement(Octet *buf, MsgManagement * manage, MsgHeader * header, PtpClock *ptpClock, const int tlvOffset) +{ + (void) header; + unpackMsgManagement(buf, manage, ptpClock); + + if ( manage->header.messageLength >= (MANAGEMENT_LENGTH + tlvOffset + TL_LENGTH) ) + { + unpackManagementTLV(buf, tlvOffset, manage, ptpClock); + + /* at this point, we know what managementTLV we have, so return and + * let someone else handle the data */ + manage->tlv->dataField = NULL; + return TRUE; + } + else /* no (more) TLV attached to this message */ + { + manage->tlv = NULL; + return FALSE; + + } + +} + +void +msgPackSignaling(Octet *buf, MsgSignaling *outgoing, PtpClock * ptpClock) +{ + DBGV("packing signaling message \n"); + (void)ptpClock; + + packMsgSignaling(outgoing, buf); +} + +/* + * Unpack Signaling message from IN buffer of ptpClock to msgtmp.signaling + * return TRUE if there are more packed TLVs left in the message. + */ +Boolean +msgUnpackSignaling(Octet *buf, MsgSignaling * signaling, MsgHeader * header, PtpClock *ptpClock, const int tlvOffset) +{ + unpackMsgSignaling(buf, signaling, ptpClock); + + if ( signaling->header.messageLength >= (SIGNALING_LENGTH + tlvOffset + TL_LENGTH) ) + { + unpackSignalingTLV(buf + tlvOffset, signaling, ptpClock); + + DBGV("Signaling seq %d: Found TLV type 0x%04x: %d bytes left\n", header->sequenceId, + signaling->tlv->tlvType,signaling->header.messageLength - SIGNALING_LENGTH - tlvOffset); + + /* at this point, we know what managementTLV we have, so return and + * let someone else handle the data */ + signaling->tlv->valueField = NULL; + return TRUE; + } + else /* no (more) TLVs attached to this message */ + { + signaling->tlv = NULL; + DBGV("Signaling seq %d: No more TLVs in message\n", header->sequenceId); + return FALSE; + } + +} + +/** + * Dump the most recent packet in the daemon + * + * @param ptpClock The central clock structure + */ +void msgDump(PtpClock *ptpClock) +{ + +#if defined(freebsd) + static int dumped = 0; +#endif /* FreeBSD */ + + msgDebugHeader(&ptpClock->msgTmpHeader); + switch (ptpClock->msgTmpHeader.messageType) { + case SYNC: + msgDebugSync(&ptpClock->msgTmp.sync); + break; + + case ANNOUNCE: + msgDebugAnnounce(&ptpClock->msgTmp.announce); + break; + + case FOLLOW_UP: + msgDebugFollowUp(&ptpClock->msgTmp.follow); + break; + + case DELAY_REQ: + msgDebugDelayReq(&ptpClock->msgTmp.req); + break; + + case DELAY_RESP: + msgDebugDelayResp(&ptpClock->msgTmp.resp); + break; + + case MANAGEMENT: + msgDebugManagement(&ptpClock->msgTmp.manage); + break; + + case SIGNALING: + NOTIFY("* msgDebugSignaling not implemented *\n"); + /* TODO: IMPLEMENT ME */ + /* msgDebugSignaling(&ptpClock->msgTmp.signaling); */ + break; + + default: + NOTIFY("msgDump:unrecognized message\n"); + break; + } + +#if defined(freebsd) + /* Only dump the first time, after that just do a message. */ + if (dumped != 0) + return; + + dumped++; + NOTIFY("msgDump: core file created.\n"); + + switch(rfork(RFFDG|RFPROC|RFNOWAIT)) { + case -1: + NOTIFY("could not fork to core dump! errno: %s", + strerror(errno)); + break; + case 0: + abort(); /* Generate a core dump */ + default: + /* This default intentionally left blank. */ + break; + } +#endif /* FreeBSD */ +} + +/** + * Dump a PTP message header + * + * @param header a pre-filled msg header structure + */ + +void msgDebugHeader(MsgHeader *header) +{ + NOTIFY("msgDebugHeader: messageType %d\n", header->messageType); + NOTIFY("msgDebugHeader: versionPTP %d\n", header->versionPTP); + NOTIFY("msgDebugHeader: messageLength %d\n", header->messageLength); + NOTIFY("msgDebugHeader: domainNumber %d\n", header->domainNumber); + NOTIFY("msgDebugHeader: flags %02hhx %02hhx\n", + header->flagField0, header->flagField1); + NOTIFY("msgDebugHeader: correctionfield %d\n", header->correctionField); + NOTIFY("msgDebugHeader: sourcePortIdentity.clockIdentity " + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx\n", + header->sourcePortIdentity.clockIdentity[0], + header->sourcePortIdentity.clockIdentity[1], + header->sourcePortIdentity.clockIdentity[2], + header->sourcePortIdentity.clockIdentity[3], + header->sourcePortIdentity.clockIdentity[4], + header->sourcePortIdentity.clockIdentity[5], + header->sourcePortIdentity.clockIdentity[6], + header->sourcePortIdentity.clockIdentity[7]); + NOTIFY("msgDebugHeader: sourcePortIdentity.portNumber %d\n", + header->sourcePortIdentity.portNumber); + NOTIFY("msgDebugHeader: sequenceId %d\n", header->sequenceId); + NOTIFY("msgDebugHeader: controlField %d\n", header->controlField); + NOTIFY("msgDebugHeader: logMessageIntervale %d\n", + header->logMessageInterval); + +} + +/** + * Dump the contents of a sync packet + * + * @param sync A pre-filled MsgSync structure + */ + +void msgDebugSync(MsgSync *sync) +{ + NOTIFY("msgDebugSync: originTimestamp.seconds %u\n", + sync->originTimestamp.secondsField); + NOTIFY("msgDebugSync: originTimestamp.nanoseconds %d\n", + sync->originTimestamp.nanosecondsField); +} + +/** + * Dump the contents of a announce packet + * + * @param sync A pre-filled MsgAnnounce structure + */ + +void msgDebugAnnounce(MsgAnnounce *announce) +{ + NOTIFY("msgDebugAnnounce: originTimestamp.seconds %u\n", + announce->originTimestamp.secondsField); + NOTIFY("msgDebugAnnounce: originTimestamp.nanoseconds %d\n", + announce->originTimestamp.nanosecondsField); + NOTIFY("msgDebugAnnounce: currentUTCOffset %d\n", + announce->currentUtcOffset); + NOTIFY("msgDebugAnnounce: grandmasterPriority1 %d\n", + announce->grandmasterPriority1); + NOTIFY("msgDebugAnnounce: grandmasterClockQuality.clockClass %d\n", + announce->grandmasterClockQuality.clockClass); + NOTIFY("msgDebugAnnounce: grandmasterClockQuality.clockAccuracy %d\n", + announce->grandmasterClockQuality.clockAccuracy); + NOTIFY("msgDebugAnnounce: " + "grandmasterClockQuality.offsetScaledLogVariance %d\n", + announce->grandmasterClockQuality.offsetScaledLogVariance); + NOTIFY("msgDebugAnnounce: grandmasterPriority2 %d\n", + announce->grandmasterPriority2); + NOTIFY("msgDebugAnnounce: grandmasterClockIdentity " + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx\n", + announce->grandmasterIdentity[0], + announce->grandmasterIdentity[1], + announce->grandmasterIdentity[2], + announce->grandmasterIdentity[3], + announce->grandmasterIdentity[4], + announce->grandmasterIdentity[5], + announce->grandmasterIdentity[6], + announce->grandmasterIdentity[7]); + NOTIFY("msgDebugAnnounce: stepsRemoved %d\n", + announce->stepsRemoved); + NOTIFY("msgDebugAnnounce: timeSource %d\n", + announce->timeSource); +} + +/** + * NOT IMPLEMENTED + * + * @param req + */ +void msgDebugDelayReq(MsgDelayReq * mdr) { + (void) mdr; +} + +/** + * Dump the contents of a followup packet + * + * @param follow A pre-fille MsgFollowUp structure + */ +void msgDebugFollowUp(MsgFollowUp *follow) +{ + NOTIFY("msgDebugFollowUp: preciseOriginTimestamp.seconds %u\n", + follow->preciseOriginTimestamp.secondsField); + NOTIFY("msgDebugFollowUp: preciseOriginTimestamp.nanoseconds %d\n", + follow->preciseOriginTimestamp.nanosecondsField); +} + +/** + * Dump the contents of a delay response packet + * + * @param resp a pre-filled MsgDelayResp structure + */ +void msgDebugDelayResp(MsgDelayResp *resp) +{ + NOTIFY("msgDebugDelayResp: delayReceiptTimestamp.seconds %u\n", + resp->receiveTimestamp.secondsField); + NOTIFY("msgDebugDelayResp: delayReceiptTimestamp.nanoseconds %d\n", + resp->receiveTimestamp.nanosecondsField); + NOTIFY("msgDebugDelayResp: requestingPortIdentity.clockIdentity " + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx\n", + resp->requestingPortIdentity.clockIdentity[0], + resp->requestingPortIdentity.clockIdentity[1], + resp->requestingPortIdentity.clockIdentity[2], + resp->requestingPortIdentity.clockIdentity[3], + resp->requestingPortIdentity.clockIdentity[4], + resp->requestingPortIdentity.clockIdentity[5], + resp->requestingPortIdentity.clockIdentity[6], + resp->requestingPortIdentity.clockIdentity[7]); + NOTIFY("msgDebugDelayResp: requestingPortIdentity.portNumber %d\n", + resp->requestingPortIdentity.portNumber); +} + +/** + * Dump the contents of management packet + * + * @param manage a pre-filled MsgManagement structure + */ + +void msgDebugManagement(MsgManagement *manage) +{ + NOTIFY("msgDebugDelayManage: targetPortIdentity.clockIdentity " + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + manage->targetPortIdentity.clockIdentity[0], + manage->targetPortIdentity.clockIdentity[1], + manage->targetPortIdentity.clockIdentity[2], + manage->targetPortIdentity.clockIdentity[3], + manage->targetPortIdentity.clockIdentity[4], + manage->targetPortIdentity.clockIdentity[5], + manage->targetPortIdentity.clockIdentity[6], + manage->targetPortIdentity.clockIdentity[7]); + NOTIFY("msgDebugDelayManage: targetPortIdentity.portNumber %d\n", + manage->targetPortIdentity.portNumber); + NOTIFY("msgDebugManagement: startingBoundaryHops %d\n", + manage->startingBoundaryHops); + NOTIFY("msgDebugManagement: boundaryHops %d\n", manage->boundaryHops); + NOTIFY("msgDebugManagement: actionField %d\n", manage->actionField); + NOTIFY("msgDebugManagement: tvl %s\n", manage->tlv); +} diff --git a/src/ptpd/src/dep/net.c b/src/ptpd/src/dep/net.c new file mode 100644 index 0000000..dc6a812 --- /dev/null +++ b/src/ptpd/src/dep/net.c @@ -0,0 +1,2304 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ +/*- + * Copyright (c) 2014-2015 Wojciech Owczarek, + * George V. Neville-Neil + * Copyright (c) 2012-2013 George V. Neville-Neil, + * Wojciech Owczarek. + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file net.c + * @date Tue Jul 20 16:17:49 2010 + * + * @brief Functions to interact with the network sockets and NIC driver. + * + * + */ + +#include "../ptpd.h" + +#ifdef PTPD_PCAP +#ifdef HAVE_PCAP_PCAP_H +#include +#else /* !HAVE_PCAP_PCAP_H */ +/* Cases like RHEL5 and others where only pcap.h exists */ +#ifdef HAVE_PCAP_H +#include +#endif /* HAVE_PCAP_H */ +#endif +#define PCAP_TIMEOUT 1 /* expressed in milliseconds */ +#endif + +#if defined PTPD_SNMP +#include +#include +#include +#endif + +/* choose kernel-level nanoseconds or microseconds resolution on the client-side */ +#if !defined(SO_TIMESTAMPING) && !defined(SO_TIMESTAMPNS) && !defined(SO_TIMESTAMP) && !defined(SO_BINTIME) +#error No kernel-level support for packet timestamping detected! +#endif + +#ifdef SO_TIMESTAMPING +#include +#include +#include +#endif /* SO_TIMESTAMPING */ + +/** + * shutdown the IPv4 multicast for specific address + * + * @param netPath + * @param multicastAddr + * + * @return TRUE if successful + */ +static Boolean +netShutdownMulticastIPv4(NetPath * netPath, Integer32 multicastAddr) +{ + struct ip_mreq imr; + + if(!multicastAddr || !netPath->interfaceAddr.s_addr) { + return TRUE; + } + + /* Close General Multicast */ + imr.imr_multiaddr.s_addr = multicastAddr; + imr.imr_interface.s_addr = netPath->interfaceAddr.s_addr; + + setsockopt(netPath->eventSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &imr, sizeof(struct ip_mreq)); + setsockopt(netPath->generalSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &imr, sizeof(struct ip_mreq)); + + return TRUE; +} + +/** + * shutdown the multicast (both General and Peer) + * + * @param netPath + * + * @return TRUE if successful + */ +static Boolean +netShutdownMulticast(NetPath * netPath) +{ + /* Close General Multicast */ + netShutdownMulticastIPv4(netPath, netPath->multicastAddr); + netPath->multicastAddr = 0; + + /* Close Peer Multicast */ + netShutdownMulticastIPv4(netPath, netPath->peerMulticastAddr); + netPath->peerMulticastAddr = 0; + + return TRUE; +} + +/* + * For future use: Check if IPv4 address is multiast - + * If first 4 bits of an address are 0xE (1110), it's multicast + */ +/* +static Boolean +isIpMulticast(struct in_addr in) +{ + if((ntohl(in.s_addr) >> 28) == 0x0E ) + return TRUE; + return FALSE; +} +*/ + +/* shut down the UDP stuff */ +Boolean +netShutdown(NetPath * netPath) +{ + netShutdownMulticast(netPath); + + /* Close sockets */ + if (netPath->eventSock >= 0) + close(netPath->eventSock); + netPath->eventSock = -1; + + if (netPath->generalSock >= 0) + close(netPath->generalSock); + netPath->generalSock = -1; + +#ifdef PTPD_PCAP + if (netPath->pcapEvent != NULL) { + pcap_close(netPath->pcapEvent); + netPath->pcapEventSock = -1; + } + if (netPath->pcapGeneral != NULL) { + pcap_close(netPath->pcapGeneral); + netPath->pcapGeneralSock = -1; + } +#endif + + freeIpv4AccessList(&netPath->timingAcl); + freeIpv4AccessList(&netPath->managementAcl); + + return TRUE; +} + +/* Check if interface ifaceName exists. Return 1 on success, 0 when interface doesn't exists, -1 on failure. + */ + +static int +interfaceExists(char* ifaceName) +{ + + int ret = -1; + struct ifaddrs *ifaddr = NULL; + struct ifaddrs *ifa = NULL; + + if(!ifaceName || !strlen(ifaceName)) { + DBG("interfaceExists called for an empty interface!"); + return 0; + } + + if(getifaddrs(&ifaddr) == -1) { + PERROR("Could not get interface list"); + return ret; + } + + for (ifa = ifaddr; ifa != NULL && ret != 1; ifa = ifa->ifa_next) { + if(ifa->ifa_name == NULL) continue; + if(!strcmp(ifaceName, ifa->ifa_name)) { + ret = 1; + } + + } + + if(ret != 1) { + ret = 0; + DBG("Interface not found: %s\n", ifaceName); + } + + freeifaddrs(ifaddr); + return ret; +} + +static int +getInterfaceFlags(char* ifaceName, unsigned int* flags) +{ + + int ret = -1; + struct ifaddrs *ifaddr = NULL; + struct ifaddrs *ifa = NULL; + + if(!ifaceName || !strlen(ifaceName)) { + DBG("interfaceExists called for an empty interface!"); + return 0; + } + + if(getifaddrs(&ifaddr) == -1) { + PERROR("Could not get interface list"); + return ret; + } + + for (ifa = ifaddr; ifa != NULL && ret != 1; ifa = ifa->ifa_next) { + if(ifa->ifa_name == NULL) continue; + if(!strcmp(ifaceName, ifa->ifa_name)) { + *flags = ifa->ifa_flags; + ret = 1; + } + } + + if(ret != 1) { + ret = 0; + DBG("Interface not found: %s\n", ifaceName); + } + + freeifaddrs(ifaddr); + return ret; +} + + +/* Try getting addr address of family family from interface ifaceName. + Return 1 on success, 0 when no suitable address available, -1 on failure. + */ +static int +getInterfaceAddress(char* ifaceName, int family, struct sockaddr* addr) { + + int ret = -1; + struct ifaddrs *ifaddr = NULL; + struct ifaddrs *ifa = NULL; + + if(!ifaceName || !strlen(ifaceName)) { + DBG("interfaceExists called for an empty interface!"); + return 0; + } + + if(getifaddrs(&ifaddr) == -1) { + PERROR("Could not get interface list"); + return ret; + } + + for (ifa = ifaddr; ifa != NULL && ret != 1; ifa = ifa->ifa_next) { + /* ifa_addr not always present - link layer may come first */ + if(ifa->ifa_name == NULL || ifa->ifa_addr == NULL) continue; + if(!strcmp(ifaceName, ifa->ifa_name) && ifa->ifa_addr->sa_family == family) { + memcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr)); + ret = 1; + } + } + + if(ret != 1) { + ret = 0; + DBG("Interface not found: %s\n", ifaceName); + } + + freeifaddrs(ifaddr); + return ret; +} + + +/* Try getting hwAddrSize bytes of ifaceName hardware address, + and place them in hwAddr. Return 1 on success, 0 when no suitable + hw address available, -1 on failure. + */ +static int +getHwAddress (char* ifaceName, unsigned char* hwAddr, int hwAddrSize) +{ + int ret = -1; + if(!ifaceName || !strlen(ifaceName)) { + return 0; + } +/* BSD* - AF_LINK gives us access to the hw address via struct sockaddr_dl */ +#if defined(AF_LINK) && !defined(__sun) + + struct ifaddrs *ifaddr = NULL; + struct ifaddrs *ifa = NULL; + + if(getifaddrs(&ifaddr) == -1) { + PERROR("Could not get interface list"); + return ret; + } + + for (ifa = ifaddr; ifa != NULL && ret == -1; ifa = ifa->ifa_next) { + if(ifa->ifa_name == NULL || ifa->ifa_addr == NULL) continue; + if(!strcmp(ifaceName, ifa->ifa_name) && ifa->ifa_addr->sa_family == AF_LINK) { + + struct sockaddr_dl* sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if(sdl->sdl_type == IFT_ETHER || sdl->sdl_type == IFT_L2VLAN) { + + memcpy(hwAddr, LLADDR(sdl), + hwAddrSize <= sizeof(sdl->sdl_data) ? + hwAddrSize : sizeof(sdl->sdl_data)); + ret = 1; + } else { + DBGV("Unsupported hardware address family on %s\n", ifaceName); + ret = 0; + } + } + } + + if(ret != 1) { + DBG("Interface not found: %s\n", ifaceName); + } + + freeifaddrs(ifaddr); + return ret; + +#else +/* Linux and Solaris family which also have SIOCGIFHWADDR/SIOCGLIFHWADDR */ + int sockfd; + struct ifreq ifr; + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + if(sockfd < 0) { + PERROR("Could not open test socket"); + return ret; + } + + memset(&ifr, 0, sizeof(ifr)); + + strncpy(ifr.ifr_name, ifaceName, IFACE_NAME_LENGTH); + + if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) { + DBGV("failed to request hardware address for %s", ifaceName); + } else { + +#ifdef HAVE_STRUCT_IFREQ_IFR_HWADDR + int af = ifr.ifr_hwaddr.sa_family; +#else + int af = ifr.ifr_addr.sa_family; +#endif /* HAVE_STRUCT_IFREQ_IFR_HWADDR */ + + if (af == ARPHRD_ETHER|| af == ARPHRD_IEEE802 +#ifdef ARPHRD_INFINIBAND + || af == ARPHRD_INFINIBAND +#endif + ) { +#ifdef HAVE_STRUCT_IFREQ_IFR_HWADDR + memcpy(hwAddr, ifr.ifr_hwaddr.sa_data, hwAddrSize); +#else + memcpy(hwAddr, ifr.ifr_addr.sa_data, hwAddrSize); +#endif /* HAVE_STRUCT_IFREQ_IFR_HWADDR */ + + ret = 1; + } else { + DBGV("Unsupported hardware address family on %s\n", ifaceName); + ret = 0; + } + } + close(sockfd); + return ret; + +#endif /* AF_LINK */ + +} + +static int getInterfaceIndex(char *ifaceName) +{ + +#ifndef SIOCGIFINDEX + return -1; +#else + int sockfd; + struct ifreq ifr; + + if(!ifaceName || !strlen(ifaceName)) { + DBG("getInterfaceIndex called for an empty interface!"); + return -1; + } + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + if(sockfd < 0) { + PERROR("Could not retrieve interface index for %s",ifaceName); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + + strncpy(ifr.ifr_name, ifaceName, IFACE_NAME_LENGTH); + + if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) { + DBGV("failed to request hardware address for %s", ifaceName); + close(sockfd); + return -1; + + } + + close(sockfd); + +#if defined(HAVE_STRUCT_IFREQ_IFR_INDEX) + return ifr.ifr_index; +#elif defined(HAVE_STRUCT_IFREQ_IFR_IFINDEX) + return ifr.ifr_ifindex; +#else + return 0; +#endif + +#endif /* !SIOCGIFINDEX */ + +} + +static Boolean getInterfaceInfo(char* ifaceName, InterfaceInfo* ifaceInfo) +{ + int res = interfaceExists(ifaceName); + + if (res == -1) { + return FALSE; + } else if (res == 0) { + ERROR("Interface %s does not exist.\n", ifaceName); + return FALSE; + } + + res = getInterfaceAddress(ifaceName, ifaceInfo->addressFamily, &ifaceInfo->afAddress); + + if (res == -1) { + return FALSE; + } + + ifaceInfo->hasAfAddress = res; + res = getHwAddress(ifaceName, (unsigned char*)ifaceInfo->hwAddress, 6); + + if (res == -1) { + return FALSE; + } + + ifaceInfo->hasHwAddress = res; + + res = getInterfaceFlags(ifaceName, &ifaceInfo->flags); + + if (res == -1) { + return FALSE; + } + + res = getInterfaceIndex(ifaceName); + + if(res == -1) { + ifaceInfo->ifIndex = 0; + } else { + ifaceInfo->ifIndex = res; + } + + return TRUE; +} + +Boolean +testInterface(char * ifaceName, const RunTimeOpts* rtOpts) +{ + InterfaceInfo info; + info.addressFamily = AF_INET; + + if(getInterfaceInfo(ifaceName, &info) != 1) { + return FALSE; + } + + switch(rtOpts->transport) { + + case UDP_IPV4: + if(!info.hasAfAddress) { + ERROR("Interface %s has no IPv4 address set\n", ifaceName); + return FALSE; + } + break; + + case IEEE_802_3: + if(!info.hasHwAddress) { + ERROR("Interface %s has no supported hardware address - possibly not an Ethernet interface\n", ifaceName); + return FALSE; + } + break; + + default: + ERROR("Unsupported transport: %d\n", rtOpts->transport); + return FALSE; + + } + + if(!(info.flags & IFF_UP) || !(info.flags & IFF_RUNNING)) { + WARNING("Interface %s seems to be down. PTPd will not operate correctly until it's up.\n", ifaceName); + } + + if(info.flags & IFF_LOOPBACK) { + WARNING("Interface %s is a loopback interface.\n", ifaceName); + } + + if(!(info.flags & IFF_MULTICAST) + && rtOpts->transport==UDP_IPV4 + && rtOpts->ipMode != IPMODE_UNICAST) { + WARNING("Interface %s is not multicast capable.\n", ifaceName); + } + + return TRUE; +} + +/** + * Init the multcast for specific IPv4 address + * + * @param netPath + * @param multicastAddr + * + * @return TRUE if successful + */ +static Boolean +netInitMulticastIPv4(NetPath * netPath, Integer32 multicastAddr) +{ + struct ip_mreq imr; + + /* multicast send only on specified interface */ + imr.imr_multiaddr.s_addr = multicastAddr; + imr.imr_interface.s_addr = netPath->interfaceAddr.s_addr; + + if (setsockopt(netPath->eventSock, IPPROTO_IP, IP_MULTICAST_IF, + &netPath->interfaceAddr, sizeof(struct in_addr)) < 0 + || setsockopt(netPath->generalSock, IPPROTO_IP, IP_MULTICAST_IF, + &netPath->interfaceAddr, sizeof(struct in_addr)) + < 0) { + PERROR("error while setting outgoig multicast interface " + "(IP_MULTICAST_IF)"); + return FALSE; + } + /* join multicast group (for receiving) on specified interface */ + if (setsockopt(netPath->eventSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &imr, sizeof(struct ip_mreq)) < 0 + || setsockopt(netPath->generalSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &imr, sizeof(struct ip_mreq)) < 0) { + PERROR("failed to join the multicast group"); + return FALSE; + } + return TRUE; +} + +/** + * Init the multcast (both General and Peer) + * + * @param netPath + * @param rtOpts + * + * @return TRUE if successful + */ +static Boolean +netInitMulticast(NetPath * netPath, const RunTimeOpts * rtOpts) +{ + struct in_addr netAddr; + char addrStr[NET_ADDRESS_LENGTH+1]; + + /* do not join multicast in unicast mode */ + if(rtOpts->ipMode == IPMODE_UNICAST) + return TRUE; + + /* Init General multicast IP address */ + strncpy(addrStr, DEFAULT_PTP_DOMAIN_ADDRESS, NET_ADDRESS_LENGTH); + if (!inet_aton(addrStr, &netAddr)) { + ERROR("failed to encode multicast address: %s\n", addrStr); + return FALSE; + } + + /* this allows for leaving groups only if joined */ + netPath->joinedGeneral = TRUE; + + netPath->multicastAddr = netAddr.s_addr; + if(!netInitMulticastIPv4(netPath, netPath->multicastAddr)) { + return FALSE; + } + + /* End of General multicast Ip address init */ + + if(rtOpts->delayMechanism != P2P) { + return TRUE; + } + + /* Init Peer multicast IP address */ + strncpy(addrStr, PEER_PTP_DOMAIN_ADDRESS, NET_ADDRESS_LENGTH); + if (!inet_aton(addrStr, &netAddr)) { + ERROR("failed to encode multicast address: %s\n", addrStr); + return FALSE; + } + + /* track if we have joined the p2p mcast group */ + netPath->joinedPeer = TRUE; + + netPath->peerMulticastAddr = netAddr.s_addr; + if(!netInitMulticastIPv4(netPath, netPath->peerMulticastAddr)) { + return FALSE; + } + /* End of Peer multicast Ip address init */ + + return TRUE; +} + +static Boolean +netSetMulticastTTL(int sockfd, int ttl) { + +#if defined(__OpenBSD__) || defined(__sun) + uint8_t temp = (uint8_t) ttl; +#else + int temp = ttl; +#endif + + if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, + &temp, sizeof(temp)) < 0) { + PERROR("Failed to set socket multicast time-to-live"); + return FALSE; + } + return TRUE; +} + +static Boolean +netSetMulticastLoopback(NetPath * netPath, Boolean value) { +#if defined(__OpenBSD__) || defined(__sun) + uint8_t temp = value ? 1 : 0; +#else + int temp = value ? 1 : 0; +#endif + DBG("Going to set multicast loopback with %d \n", temp); + + if (setsockopt(netPath->eventSock, IPPROTO_IP, IP_MULTICAST_LOOP, + &temp, sizeof(temp)) < 0) { + PERROR("Failed to set multicast loopback"); + return FALSE; + } + + return TRUE; +} + +#if defined(SO_TIMESTAMPING) && defined(SO_TIMESTAMPNS) +static Boolean +getTxTimestamp(NetPath* netPath,TimeInternal* timeStamp) { + extern PtpClock *G_ptpClock; + ssize_t length; + fd_set tmpSet; + struct timeval timeOut = {0,0}; + int val = 1; + int i = 0; + if(netPath->txTimestampFailure) + goto failure; + + FD_ZERO(&tmpSet); + FD_SET(netPath->eventSock, &tmpSet); + + if(select(netPath->eventSock + 1, &tmpSet, NULL, NULL, &timeOut) > 0) { + if (FD_ISSET(netPath->eventSock, &tmpSet)) { + + length = netRecvEvent(G_ptpClock->msgIbuf, timeStamp, + netPath, MSG_ERRQUEUE); + if (length > 0) { + DBG("getTxTimestamp: Grabbed sent msg via errqueue: %d bytes, at %d.%d\n", length, timeStamp->seconds, timeStamp->nanoseconds); + return TRUE; + } else if (length < 0) { + DBG("getTxTimestamp: Failed to poll error queue for SO_TIMESTAMPING transmit time\n"); + G_ptpClock->counters.messageRecvErrors++; + } else if (length == 0) { + DBG("getTxTimestamp: Received no data from TX error queue\n"); + } + } + } + + /* we're desperate here, aren't we... */ + for(i = 0; i < 3; i++) { + length = netRecvEvent(G_ptpClock->msgIbuf, timeStamp, netPath, MSG_ERRQUEUE); + if(length > 0) { + DBG("getTxTimestamp: SO_TIMESTAMPING - delayed TX timestamp caught\n"); + return TRUE; + } + usleep(10); + } + + /* try for the last time: sleep and poll the error queue, if nothing, consider SO_TIMESTAMPING inoperable */ + usleep(LATE_TXTIMESTAMP_US); + + length = netRecvEvent(G_ptpClock->msgIbuf, timeStamp, netPath, MSG_ERRQUEUE); + + if(length > 0) { + DBG("getTxTimestamp: SO_TIMESTAMPING - even more delayed TX timestamp caught\n"); + return TRUE; + } else { + DBG("getTxTimestamp: SO_TIMESTAMPING - TX timestamp retry failed - will use loop from now on\n"); + } + +failure: + DBG("net.c: SO_TIMESTAMPING TX software timestamp failure - reverting to SO_TIMESTAMPNS\n"); + /* unset SO_TIMESTAMPING first! otherwise we get an always-exiting select! */ + val = 0; + if(setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(int)) < 0) { + DBG("getTxTimestamp: failed to unset SO_TIMESTAMPING"); + } + val = 1; + if(setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMPNS, &val, sizeof(int)) < 0) { + DBG("getTxTimestamp: failed to revert to SO_TIMESTAMPNS"); + } + + return FALSE; +} +#endif /* SO_TIMESTAMPING */ + + +/** + * Initialize timestamping of packets + * + * @param netPath + * + * @return TRUE if successful + */ +static Boolean +netInitTimestamping(NetPath * netPath, const RunTimeOpts * rtOpts) +{ + + int val = 1; + Boolean result = TRUE; +#if defined(SO_TIMESTAMPING) && defined(SO_TIMESTAMPNS)/* Linux - current API */ + DBG("netInitTimestamping: trying to use SO_TIMESTAMPING\n"); + val = SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_RX_SOFTWARE | + SOF_TIMESTAMPING_SOFTWARE; + +/* unless compiled with PTPD_EXPERIMENTAL, check if we support the desired tstamp capabilities */ +#ifndef PTPD_EXPERIMENTAL +#ifdef ETHTOOL_GET_TS_INFO + + struct ethtool_ts_info tsInfo; + struct ifreq ifRequest; + int res; + + memset(&tsInfo, 0, sizeof(tsInfo)); + memset(&ifRequest, 0, sizeof(ifRequest)); + tsInfo.cmd = ETHTOOL_GET_TS_INFO; + strncpy( ifRequest.ifr_name, rtOpts->ifaceName, IFNAMSIZ - 1); + ifRequest.ifr_data = (char *) &tsInfo; + res = ioctl(netPath->eventSock, SIOCETHTOOL, &ifRequest); + + if (res < 0) { + PERROR("Could not retrieve ethtool timestamping capabilities for %s - reverting to SO_TIMESTAMPNS", + rtOpts->ifaceName); + val = 1; + netPath->txTimestampFailure = FALSE; + } else if((tsInfo.so_timestamping & val) != val) { + DBGV("Required SO_TIMESTAMPING flags not supported - reverting to SO_TIMESTAMPNS\n"); + val = 1; + netPath->txTimestampFailure = TRUE; + } +#else + netPath->txTimestampFailure = TRUE; + val = 1; +#endif /* ETHTOOL_GET_TS_INFO */ +#endif /* PTPD_EXPERIMENTAL */ + + if(val == 1) { + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMPNS, &val, sizeof(int)) < 0) { + PERROR("netInitTimestamping: failed to enable SO_TIMESTAMPNS"); + result = FALSE; + } + } else { + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(int)) < 0) { + PERROR("netInitTimestamping: failed to enable SO_TIMESTAMPING"); + result = FALSE; + } + } + + if (result == TRUE) { + DBG("SO_TIMESTAMP%s initialised\n",(val==1)?"NS":"ING"); + } + +#elif defined(SO_TIMESTAMPNS) /* Linux, Apple */ + DBG("netInitTimestamping: trying to use SO_TIMESTAMPNS\n"); + + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMPNS, &val, sizeof(int)) < 0) { + PERROR("netInitTimestamping: failed to enable SO_TIMESTAMPNS"); + result = FALSE; + } +#elif defined(SO_BINTIME) /* FreeBSD */ + DBG("netInitTimestamping: trying to use SO_BINTIME\n"); + + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_BINTIME, &val, sizeof(int)) < 0) { + PERROR("netInitTimestamping: failed to enable SO_BINTIME"); + result = FALSE; + } +#else + result = FALSE; +#endif + +/* fallback method */ +#if defined(SO_TIMESTAMP) /* Linux, Apple, FreeBSD */ + if (!result) { + DBG("netInitTimestamping: trying to use SO_TIMESTAMP\n"); + + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(int)) < 0) { + PERROR("netInitTimestamping: failed to enable SO_TIMESTAMP"); + result = FALSE; + } + result = TRUE; + } +#endif + + return result; +} + +Boolean +hostLookup(const char* hostname, Integer32* addr) +{ + if (hostname[0]) { + /* Attempt a DNS lookup first. */ + struct hostent *host; +#ifdef HAVE_GETHOSTBYNAME2 + host = gethostbyname2(hostname, AF_INET); +#else + host = getipnodebyname(hostname, AF_INET, AI_DEFAULT, &errno); +#endif /* HAVE_GETHOSTBYNAME2 */ + + if (host != NULL) { + if (host->h_length != 4) { + PERROR("unicast host resolved to non ipv4" + "address"); + return FALSE; + } + *addr = + *(uint32_t *)host->h_addr_list[0]; + return TRUE; + } else { + struct in_addr netAddr; + /* Maybe it's a dotted quad. */ + if (!inet_aton(hostname, &netAddr)) { + ERROR("failed to encode unicast address: %s\n", + hostname); + return FALSE; + *addr = netAddr.s_addr; + return TRUE; + } + } + } + +return FALSE; + +} + +/* parse a list of hosts to a list of IP addresses */ +static int parseUnicastConfig(const RunTimeOpts *rtOpts, int maxCount, UnicastDestination * output) +{ + char* token; + char* stash; + int found = 0; + int total = 0; + char* text_; + char* text__; + int tmp = 0; + + if(strlen(rtOpts->unicastDestinations) == 0) return 0; + + text_ = strdup(rtOpts->unicastDestinations); + + for(text__=text_;found < maxCount; text__=NULL) { + token=strtok_r(text__,", ;\t",&stash); + if(token==NULL) break; + if(hostLookup(token, &output[found].transportAddress)) { + DBG("hostList %d host: %s addr %08x\n", found, token, output[found]); + found++; + } + } + + if(text_ != NULL) { + free(text_); + } + + if(!found) { + return 0; + } + total = found; + + found = 0; + + text_=strdup(rtOpts->unicastDomains); + + for(text__=text_;found < total; text__=NULL) { + token=strtok_r(text__,", ;\t",&stash); + if(token==NULL) break; + if (sscanf(token,"%d", &tmp)) { + DBG("hostList %dth host: domain %d\n", found, tmp); + output[found].domainNumber = tmp; + found++; + } + } + + if(text_ != NULL) { + free(text_); + } + + found = 0; + text_=strdup(rtOpts->unicastLocalPreference); + + for(text__=text_;found < total; text__=NULL) { + token=strtok_r(text__,", ;\t",&stash); + tmp = LOWEST_LOCALPREFERENCE; + if(token != NULL) { + if(sscanf(token,"%d", &tmp) != 1) { + tmp = LOWEST_LOCALPREFERENCE; + } + } + + DBG("hostList %dth host: preference %d\n", found, tmp); + output[found].localPreference = tmp; + found++; + } + + if(text_ != NULL) { + free(text_); + } + + return total; +} + + + +/** + * Init all network transports + * + * @param netPath + * @param rtOpts + * @param ptpClock + * + * @return TRUE if successful + */ +Boolean +netInit(NetPath * netPath, RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + +#ifdef __QNXNTO__ + unsigned char temp; +#else + int temp; +#endif + struct sockaddr_in addr; + +#ifdef PTPD_PCAP + struct bpf_program program; + char errbuf[PCAP_ERRBUF_SIZE]; +#endif + + DBG("netInit\n"); + +#ifdef PTPD_PCAP + netPath->pcapEvent = NULL; + netPath->pcapGeneral = NULL; + netPath->pcapEventSock = -1; + netPath->pcapGeneralSock = -1; +#endif + netPath->generalSock = -1; + netPath->eventSock = -1; + +#ifdef PTPD_PCAP + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS +/* +The transportSpecific field has to be updated with 0x1(As per AUTOSAR_PRS_TimeSyncProtocol) for both Sync and FUP message. This will be taken care here, in this condition. However, as we don't set scoreConfig.autosar in ptpd arguments while running, this if never evaluated to true. Due to htis transportSpecific was always 0. + +Note: +a) scoreConfig.autosar is not used currently as it always sets the domain to 0. This option can be customized if needed as per our agreed values. + +b) Please note that for this condition to be true, we should add this option while running ptpd2 : --ptpengine:dot1as=1 +*/ + if( (rtOpts->transport == IEEE_802_3) /*&& (rtOpts->scoreConfig.autosar)*/ ) + { + netPath->headerOffset = PACKET_BEGIN_ETHER; +#ifdef HAVE_STRUCT_ETHER_ADDR_OCTET + memcpy(netPath->etherDest.octet, ether_aton(PTP_ETHER_8021AS), ETHER_ADDR_LEN); + memcpy(netPath->peerEtherDest.octet, ether_aton(PTP_ETHER_8021AS), ETHER_ADDR_LEN); +#else + memcpy(netPath->etherDest.ether_addr_octet, ether_aton(PTP_ETHER_8021AS), ETHER_ADDR_LEN); + memcpy(netPath->peerEtherDest.ether_addr_octet, ether_aton(PTP_ETHER_8021AS), ETHER_ADDR_LEN); +#endif /* HAVE_STRUCT_ETHER_ADDR_OCTET */ + + } + else /* the following "if" will make this else to an "else if" */ +#endif + // ##### SCORE MODIFICATION END ##### + if (rtOpts->transport == IEEE_802_3) { + netPath->headerOffset = PACKET_BEGIN_ETHER; +#ifdef HAVE_STRUCT_ETHER_ADDR_OCTET + memcpy(netPath->etherDest.octet, ether_aton(PTP_ETHER_DST), ETHER_ADDR_LEN); + memcpy(netPath->peerEtherDest.octet, ether_aton(PTP_ETHER_PEER), ETHER_ADDR_LEN); +#else + memcpy(netPath->etherDest.ether_addr_octet, ether_aton(PTP_ETHER_DST), ETHER_ADDR_LEN); + memcpy(netPath->peerEtherDest.ether_addr_octet, ether_aton(PTP_ETHER_PEER), ETHER_ADDR_LEN); +#endif /* HAVE_STRUCT_ETHER_ADDR_OCTET */ + } else +#endif + netPath->headerOffset = PACKET_BEGIN_UDP; + + /* open sockets */ + if ((netPath->eventSock = socket(PF_INET, SOCK_DGRAM, + IPPROTO_UDP)) < 0 + || (netPath->generalSock = socket(PF_INET, SOCK_DGRAM, + IPPROTO_UDP)) < 0) { + PERROR("failed to initialize sockets"); + return FALSE; + } + + /* let's see if we have another interface left before we die */ + if(!testInterface(rtOpts->ifaceName, rtOpts)) { + + /* backup not enabled - exit */ + if(!rtOpts->backupIfaceEnabled) + return FALSE; + + /* backup enabled - try the other interface */ + ptpClock->runningBackupInterface = !ptpClock->runningBackupInterface; + + rtOpts->ifaceName = (ptpClock->runningBackupInterface)?rtOpts->backupIfaceName : rtOpts->primaryIfaceName; + + NOTICE("Last resort - attempting to switch to %s interface\n", ptpClock->runningBackupInterface ? "backup" : "primary"); + /* if this fails, we have no reason to live */ + if(!testInterface(rtOpts->ifaceName, rtOpts)) { + return FALSE; + } + } + + + netPath->interfaceInfo.addressFamily = AF_INET; + + /* the if is here only to get rid of an unused result warning. */ + if( getInterfaceInfo(rtOpts->ifaceName, &netPath->interfaceInfo)!= 1) + return FALSE; + + /* No HW address, we'll use the protocol address to form interfaceID -> clockID */ + if( !netPath->interfaceInfo.hasHwAddress && netPath->interfaceInfo.hasAfAddress ) { + uint32_t addr = ((struct sockaddr_in*)&(netPath->interfaceInfo.afAddress))->sin_addr.s_addr; + memcpy(netPath->interfaceID, &addr, 2); + memcpy(netPath->interfaceID + 4,((uint8_t*)&addr) + 2, 2); + /* Initialise interfaceID with hardware address */ + } else { + memcpy(&netPath->interfaceID, &netPath->interfaceInfo.hwAddress, + sizeof(netPath->interfaceID) <= sizeof(netPath->interfaceInfo.hwAddress) ? + sizeof(netPath->interfaceID) : sizeof(netPath->interfaceInfo.hwAddress) + ); + } + + DBG("Listening on IP: %s\n",inet_ntoa( + ((struct sockaddr_in*)&(netPath->interfaceInfo.afAddress))->sin_addr)); + +#ifdef PTPD_PCAP + if (rtOpts->pcap == TRUE) { + + netPath->txTimestampFailure = TRUE; + + int promisc = (rtOpts->transport == IEEE_802_3 ) ? 1 : 0; + + if ((netPath->pcapEvent = pcap_open_live(rtOpts->ifaceName, + PACKET_SIZE, promisc, + PCAP_TIMEOUT, + errbuf)) == NULL) { + PERROR("failed to open event pcap"); + return FALSE; + } + +/* libpcap - new way - may be required for non-default buffer sizes */ +/* + netPath->pcapEvent = pcap_create(rtOpts->ifaceName, errbuf); + pcap_set_promisc(netPath->pcapEvent, promisc); + pcap_set_snaplen(netPath->pcapEvent, PACKET_SIZE); + pcap_set_timeout(netPath->pcapEvent, PCAP_TIMEOUT); + pcap_set_buffer_size(netPath->pcapEvent, 1024 * 2 * UNICAST_MAX_DESTINATIONS); + pcap_activate(netPath->pcapEvent); +*/ + if (pcap_compile(netPath->pcapEvent, &program, + ( rtOpts->transport == IEEE_802_3 ) ? + "ether proto 0x88f7": + ( rtOpts->ipMode == IPMODE_UNICAST ) ? + "udp port 319 and not multicast" : + ( rtOpts->ipMode != IPMODE_MULTICAST ) ? + "udp port 319" : + "host (224.0.1.129 or 224.0.0.107) and udp port 319" , + 1, 0) < 0) { + PERROR("failed to compile pcap event filter"); + pcap_perror(netPath->pcapEvent, "ptpd2"); + return FALSE; + } + if (pcap_setfilter(netPath->pcapEvent, &program) < 0) { + PERROR("failed to set pcap event filter"); + return FALSE; + } + pcap_freecode(&program); + if ((netPath->pcapEventSock = + pcap_get_selectable_fd(netPath->pcapEvent)) < 0) { + PERROR("failed to get pcap event fd"); + return FALSE; + } + if ((netPath->pcapGeneral = pcap_open_live(rtOpts->ifaceName, + PACKET_SIZE, promisc, + PCAP_TIMEOUT, + errbuf)) == NULL) { + PERROR("failed to open general pcap"); + return FALSE; + } + if (rtOpts->transport != IEEE_802_3) { + if (pcap_compile(netPath->pcapGeneral, &program, + ( rtOpts->ipMode == IPMODE_UNICAST ) ? + "udp port 320 and not multicast" : + ( rtOpts->ipMode != IPMODE_MULTICAST ) ? + "udp port 320" : + "host (224.0.1.129 or 224.0.0.107) and udp port 320" , + 1, 0) < 0) { + PERROR("failed to compile pcap general filter"); + pcap_perror(netPath->pcapGeneral, "ptpd2"); + return FALSE; + } + if (pcap_setfilter(netPath->pcapGeneral, &program) < 0) { + PERROR("failed to set pcap general filter"); + return FALSE; + } + pcap_freecode(&program); + if ((netPath->pcapGeneralSock = + pcap_get_selectable_fd(netPath->pcapGeneral)) < 0) { + PERROR("failed to get pcap general fd"); + return FALSE; + } + } + } +#endif + +#ifdef PTPD_PCAP + if(rtOpts->transport == IEEE_802_3) { + close(netPath->eventSock); + netPath->eventSock = -1; + close(netPath->generalSock); + netPath->generalSock = -1; + /* TX timestamp is not generated for PCAP mode and Ethernet transport */ +#ifdef SO_TIMESTAMPING + netPath->txTimestampFailure = TRUE; +#endif /* SO_TIMESTAMPING */ + } else { +#endif + /* save interface address for IGMP refresh */ + { + struct sockaddr_in* sin = (struct sockaddr_in*)&(netPath->interfaceInfo.afAddress); + netPath->interfaceAddr = sin->sin_addr; + } + + DBG("Local IP address used : %s \n", inet_ntoa(netPath->interfaceAddr)); + + temp = 1; /* allow address reuse */ + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_REUSEADDR, + &temp, sizeof(int)) < 0 + || setsockopt(netPath->generalSock, SOL_SOCKET, SO_REUSEADDR, + &temp, sizeof(int)) < 0) { + DBG("failed to set socket reuse\n"); + } + + /* disable UDP checksum validation (Linux) */ +#ifdef SO_NO_CHECK + if(rtOpts->disableUdpChecksums) { + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_NO_CHECK , &temp, + sizeof(int)) < 0 + || setsockopt(netPath->generalSock, SOL_SOCKET, SO_NO_CHECK , &temp, + sizeof(int)) < 0) { + WARNING("Could not disable UDP checksum validation\n"); + } + } +#endif /* SO_NO_CHECK */ + + /* bind sockets */ + /* + * need INADDR_ANY to receive both unicast and multicast, + * but only need interface address for unicast + */ + + if(rtOpts->ipMode == IPMODE_UNICAST || + rtOpts->ignore_daemon_lock) { + addr.sin_addr = netPath->interfaceAddr; + } else { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(PTP_EVENT_PORT); + if (bind(netPath->eventSock, (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)) < 0) { + PERROR("failed to bind event socket"); + return FALSE; + } + addr.sin_port = htons(PTP_GENERAL_PORT); + if (bind(netPath->generalSock, (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)) < 0) { + PERROR("failed to bind general socket"); + return FALSE; + } + +#ifdef SO_RCVBUF + /* try increasing receive buffers for unicast Sync processing */ + if(rtOpts->ipMode == IPMODE_UNICAST && !rtOpts->slaveOnly) { + uint32_t n = 0; + socklen_t nlen = sizeof(n); + + if (getsockopt(netPath->eventSock, SOL_SOCKET, SO_RCVBUF, &n, &nlen) < 0) { + n = 0; + } + + DBG("eventSock rcvbuff : %d\n", n); + + if(n < (UNICAST_MAX_DESTINATIONS * 1024)) { + n = UNICAST_MAX_DESTINATIONS * 1024; + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) < 0) { + DBG("Failed to increase event socket receive buffer\n"); + } + } + + if (getsockopt(netPath->generalSock, SOL_SOCKET, SO_RCVBUF, &n, &nlen) < 0) { + n = 0; + } + + DBG("genetalSock rcvbuff : %d\n", n); + + if(n < (UNICAST_MAX_DESTINATIONS * 1024)) { + n = UNICAST_MAX_DESTINATIONS * 1024; + if (setsockopt(netPath->generalSock, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) < 0) { + DBG("Failed to increase general socket receive buffer\n"); + } + } + } +#endif /* SO_RCVBUF */ + +#ifdef USE_BINDTODEVICE +#ifdef linux + /* + * The following code makes sure that the data is only + * received on the specified interface. Without this option, + * it's possible to receive PTP from another interface, and + * confuse the protocol. Calling bind() with the IP address + * of the device instead of INADDR_ANY does not work. + * + * More info: + * http://developerweb.net/viewtopic.php?id=6471 + * http://stackoverflow.com/questions/1207746/problems-with-so-bindtodevice-linux-socket-option + */ + + /* + * wowczarek: 2.3.1-rc4@jun0215: this breaks the manual packet looping, + * so may only be used for multicast-only + */ + + if ( rtOpts->ipMode == IPMODE_MULTICAST ) { + if (setsockopt(netPath->eventSock, SOL_SOCKET, SO_BINDTODEVICE, + rtOpts->ifaceName, strlen(rtOpts->ifaceName)) < 0 + || setsockopt(netPath->generalSock, SOL_SOCKET, SO_BINDTODEVICE, + rtOpts->ifaceName, strlen(rtOpts->ifaceName)) < 0){ + PERROR("failed to call SO_BINDTODEVICE on the interface"); + return FALSE; + } + } + +#endif +#endif + + /* Set socket dscp */ + if(rtOpts->dscpValue) { + + if (setsockopt(netPath->eventSock, IPPROTO_IP, IP_TOS, + &rtOpts->dscpValue, sizeof(int)) < 0 + || setsockopt(netPath->generalSock, IPPROTO_IP, IP_TOS, + &rtOpts->dscpValue, sizeof(int)) < 0) { + PERROR("Failed to set socket DSCP bits"); + return FALSE; + } + } + + if(rtOpts->unicastDestinationsSet) { + + ptpClock->unicastDestinationCount = parseUnicastConfig(rtOpts, + UNICAST_MAX_DESTINATIONS, ptpClock->unicastDestinations); + DBG("configured %d unicast destinations\n",ptpClock->unicastDestinationCount); + + } + + if(rtOpts->delayMechanism==P2P && rtOpts->ipMode==IPMODE_UNICAST) { + ptpClock->unicastPeerDestination.transportAddress = 0; + if(rtOpts->unicastPeerDestinationSet && + rtOpts->delayMechanism==P2P && !hostLookup(rtOpts->unicastPeerDestination, + &ptpClock->unicastPeerDestination.transportAddress)) { + + ERROR("Could not parse P2P unicast destination %s:\n", + rtOpts->unicastPeerDestination); + return FALSE; + + } else if(!rtOpts->unicastPeerDestinationSet) { + + ERROR("No P2P unicast destination specified\n"); + return FALSE; + + } + + } + + if(rtOpts->ipMode != IPMODE_UNICAST) { + + /* init UDP Multicast on both Default and Peer addresses */ + if (!netInitMulticast(netPath, rtOpts)) + return FALSE; + + /* set socket time-to-live */ + if(!netSetMulticastTTL(netPath->eventSock,rtOpts->ttl) || + !netSetMulticastTTL(netPath->generalSock,rtOpts->ttl)) + return FALSE; + + /* start tracking TTL */ + netPath->ttlEvent = rtOpts->ttl; + netPath->ttlGeneral = rtOpts->ttl; + } + + /* try enabling the capture of destination address */ + +#ifdef IP_PKTINFO + temp = 1; + setsockopt(netPath->eventSock, IPPROTO_IP, IP_PKTINFO, &temp, sizeof(int)); +#endif /* IP_PKTINFO */ + +#ifdef IP_RECVDSTADDR + temp = 1; + setsockopt(netPath->eventSock, IPPROTO_IP, IP_RECVDSTADDR, &temp, sizeof(int)); +#endif + + +#ifdef SO_TIMESTAMPING + /* Reset the failure indicator when (re)starting network */ + netPath->txTimestampFailure = FALSE; + /* for SO_TIMESTAMPING we're receiving transmitted packets via ERRQUEUE */ + temp = 0; +#else + /* enable loopback */ + temp = 1; +#endif + + /* make timestamps available through recvmsg() */ + if (!netInitTimestamping(netPath,rtOpts)) { + ERROR("Failed to enable packet time stamping\n"); + return FALSE; + } + +#ifdef SO_TIMESTAMPING + /* If we failed to initialise SO_TIMESTAMPING, enable mcast loopback */ + if(netPath->txTimestampFailure) + temp = 1; +#endif + + if(!netSetMulticastLoopback(netPath, temp)) { + return FALSE; + } + +#ifdef PTPD_PCAP + } +#endif + + /* Compile ACLs */ + if(rtOpts->timingAclEnabled) { + freeIpv4AccessList(&netPath->timingAcl); + netPath->timingAcl=createIpv4AccessList(rtOpts->timingAclPermitText, + rtOpts->timingAclDenyText, rtOpts->timingAclOrder); + } + if(rtOpts->managementAclEnabled) { + freeIpv4AccessList(&netPath->managementAcl); + netPath->managementAcl=createIpv4AccessList(rtOpts->managementAclPermitText, + rtOpts->managementAclDenyText, rtOpts->managementAclOrder); + } + + + return TRUE; +} + +/*Check if data has been received*/ +int +netSelect(TimeInternal * timeout, NetPath * netPath, fd_set *readfds) +{ + int ret, nfds; + struct timeval tv, *tv_ptr; + + +#if defined PTPD_SNMP + extern const RunTimeOpts rtOpts; + struct timeval snmp_timer_wait = { 0, 0}; // initialise to avoid unused warnings when SNMP disabled + int snmpblock = 0; +#endif + + if (timeout) { + if(isTimeInternalNegative(timeout)) { + ERROR("Negative timeout attempted for select()\n"); + return -1; + } + tv.tv_sec = timeout->seconds; + tv.tv_usec = timeout->nanoseconds / 1000; + tv_ptr = &tv; + } else { + tv_ptr = NULL; + } + + FD_ZERO(readfds); + nfds = 0; +#ifdef PTPD_PCAP + if (netPath->pcapEventSock >= 0) { + FD_SET(netPath->pcapEventSock, readfds); + if (netPath->pcapGeneralSock >= 0) + FD_SET(netPath->pcapGeneralSock, readfds); + + nfds = netPath->pcapEventSock; + if (netPath->pcapEventSock < netPath->pcapGeneralSock) + nfds = netPath->pcapGeneralSock; + + } else if (netPath->eventSock >= 0) { +#endif + FD_SET(netPath->eventSock, readfds); + if (netPath->generalSock >= 0) + FD_SET(netPath->generalSock, readfds); + + nfds = netPath->eventSock; + if (netPath->eventSock < netPath->generalSock) + nfds = netPath->generalSock; +#ifdef PTPD_PCAP + } +#endif + nfds++; + +#if defined PTPD_SNMP +if (rtOpts.snmpEnabled) { + snmpblock = 1; + if (tv_ptr) { + snmpblock = 0; + memcpy(&snmp_timer_wait, tv_ptr, sizeof(struct timeval)); + } + snmp_select_info(&nfds, readfds, &snmp_timer_wait, &snmpblock); + if (snmpblock == 0) + tv_ptr = &snmp_timer_wait; +} +#endif + + ret = select(nfds, readfds, 0, 0, tv_ptr); + + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + } +#if defined PTPD_SNMP +if (rtOpts.snmpEnabled) { + /* Maybe we have received SNMP related data */ + if (ret > 0) { + snmp_read(readfds); + } else if (ret == 0) { + snmp_timeout(); + run_alarms(); + } + netsnmp_check_outstanding_agent_requests(); +} +#endif + return ret; +} + +/** + * store received data from network to "buf" , get and store the + * SO_TIMESTAMP value in "time" for an event message + * + * @note Should this function be merged with netRecvGeneral(), below? + * Jan Breuer: I think that netRecvGeneral should be + * simplified. Timestamp returned by this function is never + * used. According to this, netInitTimestamping can be also simplified + * to initialize timestamping only on eventSock. + * + * @param buf + * @param time + * @param netPath + * + * @return + */ + +ssize_t +netRecvEvent(Octet * buf, TimeInternal * time, NetPath * netPath, int flags) +{ + ssize_t ret = 0; + struct msghdr msg; + struct iovec vec[1]; + struct sockaddr_in from_addr; + +#ifdef PTPD_PCAP + struct pcap_pkthdr *pkt_header; + const u_char *pkt_data; +#endif + +#if defined(__QNXNTO__) && defined(PTPD_EXPERIMENTAL) + TimeInternal tmpTime; + /* get system time interpolated with TSC / clockCycles as soon as we have data on the socket */ + getTime(&tmpTime); +#endif + + union { + struct cmsghdr cm; + char control[256]; + } cmsg_un; + + struct cmsghdr *cmsg; + +#if defined(SO_TIMESTAMPNS) || defined(SO_TIMESTAMPING) + struct timespec * ts; +#elif defined(SO_BINTIME) + struct bintime * bt; + struct timespec ts; +#endif + +#if defined(SO_TIMESTAMP) + struct timeval * tv; +#endif + Boolean timestampValid = FALSE; + netPath->lastDestAddr = 0; +#ifdef PTPD_PCAP + if (netPath->pcapEvent == NULL) { /* Using sockets */ +#endif + vec[0].iov_base = buf; + vec[0].iov_len = PACKET_SIZE; + + memset(&msg, 0, sizeof(msg)); + memset(&from_addr, 0, sizeof(from_addr)); + memset(buf, 0, PACKET_SIZE); + memset(&cmsg_un, 0, sizeof(cmsg_un)); + + msg.msg_name = (caddr_t)&from_addr; + msg.msg_namelen = sizeof(from_addr); + msg.msg_iov = vec; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_un.control; + msg.msg_controllen = sizeof(cmsg_un.control); + msg.msg_flags = 0; + + ret = recvmsg(netPath->eventSock, &msg, flags | MSG_DONTWAIT); + if (ret <= 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return ret; + }; + if (msg.msg_flags & MSG_TRUNC) { + ERROR("received truncated message\n"); + return 0; + } + /* get time stamp of packet */ + if (!time) { + ERROR("null receive time stamp argument\n"); + return 0; + } + if (msg.msg_flags & MSG_CTRUNC) { + ERROR("received truncated ancillary data\n"); + return 0; + } + +#if defined(HAVE_DECL_MSG_ERRQUEUE) && HAVE_DECL_MSG_ERRQUEUE + if(!(flags & MSG_ERRQUEUE)) +#endif + netPath->lastSourceAddr = from_addr.sin_addr.s_addr; + + netPath->receivedPacketsTotal++; + + /* do not report "from self" */ + if(!netPath->lastSourceAddr || (netPath->lastSourceAddr != netPath->interfaceAddr.s_addr)) { + netPath->receivedPackets++; + } + + if (msg.msg_controllen <= 0) { + ERROR("received short ancillary data (%ld/%ld)\n", + (long)msg.msg_controllen, (long)sizeof(cmsg_un.control)); + + return 0; + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + +#ifdef IP_PKTINFO + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + struct in_pktinfo *pi = + (struct in_pktinfo *) CMSG_DATA(cmsg); + netPath->lastDestAddr = pi->ipi_addr.s_addr; + DBG("IP_PKTINFO Dst: %s\n", inet_ntoa(pi->ipi_addr)); + } +#endif + +#ifdef IP_RECVDSTADDR + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_RECVDSTADDR)) { + struct in_addr *pa = (struct in_addr *) CMSG_DATA(cmsg); + netPath->lastDestAddr = pa->s_addr; + DBG("IP_RECVDSTADDR Dst: %s\n", inet_ntoa(*pa)); + } +#endif + + + if (cmsg->cmsg_level == SOL_SOCKET) { +#if defined(SO_TIMESTAMPING) && defined(SO_TIMESTAMPNS) + if(cmsg->cmsg_type == SO_TIMESTAMPING || + cmsg->cmsg_type == SO_TIMESTAMPNS) { + ts = (struct timespec *)CMSG_DATA(cmsg); + time->seconds = ts->tv_sec; + time->nanoseconds = ts->tv_nsec; + timestampValid = TRUE; + DBG("rcvevent: SO_TIMESTAMP%s %s time stamp: %us %dns\n", netPath->txTimestampFailure ? + "NS" : "ING", + (flags & MSG_ERRQUEUE) ? "(TX)" : "(RX)" , time->seconds, time->nanoseconds); + break; + } +#elif defined(SO_TIMESTAMPNS) + if(cmsg->cmsg_type == SCM_TIMESTAMPNS) { + ts = (struct timespec *)CMSG_DATA(cmsg); + time->seconds = ts->tv_sec; + time->nanoseconds = ts->tv_nsec; + timestampValid = TRUE; + DBGV("kernel NANO recv time stamp %us %dns\n", + time->seconds, time->nanoseconds); + break; + } + +/* +qnx8_introduction: + This part of the code gets enabled in qnx8 as the macro SO_BINTIME is defined in new sdp - target\qnx\usr\include\sys\socket.h + Due to this, the function bintime2timespec gets called. However, the function bintime2timespec + does not get resolved. The linker throws undefined reference to `bintime2timespec' error + In score-ptp-daemon, we never use IP based timestamping. We use raw ethernet based communication + for both QNX and Linux. + Hence we disable this part of the code for now. +*/ +/* #elif defined(SO_BINTIME) + if(cmsg->cmsg_type == SCM_BINTIME) { + bt = (struct bintime *)CMSG_DATA(cmsg); + bintime2timespec(bt, &ts); + time->seconds = ts.tv_sec; + time->nanoseconds = ts.tv_nsec; + timestampValid = TRUE; + DBGV("kernel NANO recv time stamp %us %dns\n", + time->seconds, time->nanoseconds); + break; + } +*/ +#endif + +#if defined(SO_TIMESTAMP) + if(cmsg->cmsg_type == SCM_TIMESTAMP) { + tv = (struct timeval *)CMSG_DATA(cmsg); + time->seconds = tv->tv_sec; + time->nanoseconds = tv->tv_usec * 1000; + timestampValid = TRUE; + DBGV("kernel MICRO recv time stamp %us %dns\n", + time->seconds, time->nanoseconds); + } +#endif + } + + } + + + if (!timestampValid) { + /* + * do not try to get by with recording the time here, better + * to fail because the time recorded could be well after the + * message receive, which would put a big spike in the + * offset signal sent to the clock servo + */ + DBG("netRecvEvent: no receive time stamp\n"); + return 0; + } +#ifdef PTPD_PCAP + } +#endif + +#ifdef PTPD_PCAP + else { /* Using PCAP */ + /* Discard packet on socket */ + if (netPath->eventSock >= 0) { + recv(netPath->eventSock, buf, PACKET_SIZE, MSG_DONTWAIT); + } + + if ((ret = pcap_next_ex(netPath->pcapEvent, &pkt_header, + &pkt_data)) < 1) { + if (ret < 0) + INFO("netRecvEvent: pcap_next_ex failed %s\n", + pcap_geterr(netPath->pcapEvent)); + return 0; + } + + /* Make sure this is IP (could dot1q get here?) */ + if( ntohs(*(u_short *)(pkt_data + 12)) != ETHERTYPE_IP) { + if( ntohs(*(u_short *)(pkt_data + 12)) != PTP_ETHER_TYPE) { + DBG("PCAP payload ethertype received not IP or PTP: 0x%04x\n", + ntohs(*(u_short *)(pkt_data + 12))); + /* do not count packets if from self */ + } else if(memcmp(&netPath->interfaceInfo.hwAddress, pkt_data + 6, 6)) { + netPath->receivedPackets++; + } + } else { + /* Retrieve source IP from the payload - 14 eth + 12 IP */ + netPath->lastSourceAddr = *(Integer32 *)(pkt_data + 26); + /* Retrieve destination IP from the payload - 14 eth + 16 IP */ + netPath->lastDestAddr = *(Integer32 *)(pkt_data + 30); + /* do not count packets from self */ + if(netPath->lastSourceAddr != netPath->interfaceAddr.s_addr) { + netPath->receivedPackets++; + } + } + + netPath->receivedPacketsTotal++; + + /* XXX Total cheat */ + memcpy(buf, pkt_data + netPath->headerOffset, + pkt_header->caplen - netPath->headerOffset); + time->seconds = pkt_header->ts.tv_sec; + time->nanoseconds = pkt_header->ts.tv_usec * 1000; + timestampValid = TRUE; + DBGV("netRecvEvent: kernel PCAP recv time stamp %us %dns\n", + time->seconds, time->nanoseconds); + fflush(NULL); + ret = pkt_header->caplen - netPath->headerOffset; + } +#endif + +#if defined(__QNXNTO__) && defined(PTPD_EXPERIMENTAL) + *time = tmpTime; +#endif + + return ret; +} + + + +/** + * + * store received data from network to "buf" get and store the + * SO_TIMESTAMP value in "time" for a general message + * + * @param buf + * @param time + * @param netPath + * + * @return + */ + +ssize_t +netRecvGeneral(Octet * buf, NetPath * netPath) +{ + ssize_t ret = 0; + struct sockaddr_in from_addr; + +#ifdef PTPD_PCAP + struct pcap_pkthdr *pkt_header; + const u_char *pkt_data; +#endif + socklen_t from_addr_len = sizeof(from_addr); + + netPath->lastSourceAddr = 0; + +#ifdef PTPD_PCAP + if (netPath->pcapGeneral == NULL) { +#endif + ret=recvfrom(netPath->generalSock, buf, PACKET_SIZE, MSG_DONTWAIT, (struct sockaddr*)&from_addr, &from_addr_len); + netPath->lastSourceAddr = from_addr.sin_addr.s_addr; + + /* do not report "from self" */ + if(!netPath->lastSourceAddr || (netPath->lastSourceAddr != netPath->interfaceAddr.s_addr)) { + netPath->receivedPackets++; + } + netPath->receivedPacketsTotal++; + return ret; +#ifdef PTPD_PCAP + } +#endif + +#ifdef PTPD_PCAP + else { /* Using PCAP */ + /* Discard packet on socket */ + if (netPath->generalSock >= 0) + recv(netPath->generalSock, buf, PACKET_SIZE, MSG_DONTWAIT); + + + if (( ret = pcap_next_ex(netPath->pcapGeneral, &pkt_header, + &pkt_data)) < 1) { + if (ret < 0) + DBGV("netRecvGeneral: pcap_next_ex failed %d %s\n", + ret, pcap_geterr(netPath->pcapGeneral)); + return 0; + } + + /* Make sure this is IP (could dot1q get here?) */ + if( ntohs(*(u_short *)(pkt_data + 12)) != ETHERTYPE_IP) { + if( ntohs(*(u_short *)(pkt_data + 12)) != PTP_ETHER_TYPE) { + DBG("PCAP payload ethertype received not IP or PTP: 0x%04x\n", + ntohs(*(u_short *)(pkt_data + 12))); + /* do not count packets if from self */ + } else if(memcmp(&netPath->interfaceInfo.hwAddress, pkt_data + 6, 6)) { + netPath->receivedPackets++; + } + } else { + /* Retrieve source IP from the payload - 14 eth + 12 IP */ + netPath->lastSourceAddr = *(Integer32 *)(pkt_data + 26); + /* Retrieve destination IP from the payload - 14 eth + 16 IP */ + netPath->lastDestAddr = *(Integer32 *)(pkt_data + 30); + /* do not count packets from self */ + if(netPath->lastSourceAddr != netPath->interfaceAddr.s_addr) { + netPath->receivedPackets++; + } + } + + netPath->receivedPacketsTotal++; + + /* XXX Total cheat */ + memcpy(buf, pkt_data + netPath->headerOffset, + pkt_header->caplen - netPath->headerOffset); + fflush(NULL); + ret = pkt_header->caplen - netPath->headerOffset; + } +#endif + return ret; +} + + +#ifdef PTPD_PCAP +ssize_t +netSendPcapEther(Octet * buf, UInteger16 length, + struct ether_addr * dst, struct ether_addr * src, + pcap_t * pcap) { + Octet ether[ETHER_HDR_LEN + PACKET_SIZE]; +#ifdef HAVE_STRUCT_ETHER_ADDR_OCTET + memcpy(ether, dst->octet, ETHER_ADDR_LEN); + memcpy(ether + ETHER_ADDR_LEN, src->octet, ETHER_ADDR_LEN); +#else + memcpy(ether, dst->ether_addr_octet, ETHER_ADDR_LEN); + memcpy(ether + ETHER_ADDR_LEN, src->ether_addr_octet, ETHER_ADDR_LEN); +#endif /* HAVE_STRUCT_ETHER_ADDR_OCTET */ + *((short *)ðer[2 * ETHER_ADDR_LEN]) = htons(PTP_ETHER_TYPE); + memcpy(ether + ETHER_HDR_LEN, buf, length); + + return pcap_inject(pcap, ether, ETHER_HDR_LEN + length); +} +#endif + +// +// destinationAddress: destination: +// if filled, send to this unicast dest; +// if zero, sending to multicast. +// +/// +/// TODO: merge these 2 functions into one +/// +ssize_t +netSendEvent(Octet * buf, UInteger16 length, NetPath * netPath, + const RunTimeOpts *rtOpts, Integer32 destinationAddress, TimeInternal * tim) +{ + ssize_t ret; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(PTP_EVENT_PORT); + +#if defined(__QNXNTO__) && defined(PTPD_EXPERIMENTAL) + TimeInternal tmpTime; + /* get system time interpolated with TSC / clockCycles as soon as we have data on the socket */ + getTime(&tmpTime); +#endif + +#ifdef PTPD_PCAP + + /* In PCAP Ethernet mode, we use pcapEvent for receiving all messages + * and pcapGeneral for sending all messages + */ + if ((netPath->pcapGeneral != NULL) && (rtOpts->transport == IEEE_802_3 )) { + ret = netSendPcapEther(buf, length, + &netPath->etherDest, + (struct ether_addr *)netPath->interfaceID, + netPath->pcapGeneral); + if (ret <= 0) + DBG("Error sending ether multicast event message\n"); + else { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } + } else { +#endif + if (destinationAddress ) { + addr.sin_addr.s_addr = destinationAddress; + /* + * This function is used for PTP only anyway - for now. + * If we're sending to a unicast address, set the UNICAST flag. + * Transport API in LibCCK / 2.4 uses a callback for this, + * so the client can do something with the payload before it's sent, + * depending if it's unicast or multicast. + */ + *(char *)(buf + 6) |= PTP_UNICAST; + + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending unicast event message\n"); + else { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } +#ifndef SO_TIMESTAMPING +#if defined(__QNXNTO__) && defined(PTPD_EXPERIMENTAL) + *tim = tmpTime; +#else + /* + * Need to forcibly loop back the packet since + * we are not using multicast. + */ + addr.sin_addr.s_addr = netPath->interfaceAddr.s_addr; + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBGV("Error looping back unicast event message\n"); +#endif + +#else + +#ifdef PTPD_PCAP + if((netPath->pcapEvent == NULL) && !netPath->txTimestampFailure) { +#else + if(!netPath->txTimestampFailure) { +#endif /* PTPD_PCAP */ + if(!getTxTimestamp(netPath, tim)) { + netPath->txTimestampFailure = TRUE; + if (tim) { + clearTime(tim); + } + } + } + + if(netPath->txTimestampFailure) + { + /* We've had a TX timestamp receipt timeout - falling back to packet looping */ + addr.sin_addr.s_addr = netPath->interfaceAddr.s_addr; + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error looping back unicast event message\n"); + } +#endif /* SO_TIMESTAMPING */ + } else { + addr.sin_addr.s_addr = netPath->multicastAddr; + /* Is TTL OK? */ + if(netPath->ttlEvent != rtOpts->ttl) { + /* Try restoring TTL */ + /* set socket time-to-live */ + if (netSetMulticastTTL(netPath->eventSock,rtOpts->ttl)) { + netPath->ttlEvent = rtOpts->ttl; + } + } + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending multicast event message\n"); + else { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } +#ifdef SO_TIMESTAMPING + +#ifdef PTPD_PCAP + if((netPath->pcapEvent == NULL) && !netPath->txTimestampFailure) { +#else + if(!netPath->txTimestampFailure) { +#endif /* PTPD_PCAP */ + if(!getTxTimestamp(netPath, tim)) { + if (tim) { + clearTime(tim); + } + + netPath->txTimestampFailure = TRUE; + + /* Try re-enabling MULTICAST_LOOP */ + netSetMulticastLoopback(netPath, TRUE); + } + } +#endif /* SO_TIMESTAMPING */ + } + +#ifdef PTPD_PCAP + } +#endif + return ret; +} + +ssize_t +netSendGeneral(Octet * buf, UInteger16 length, NetPath * netPath, + const const RunTimeOpts *rtOpts, Integer32 destinationAddress) +{ + ssize_t ret; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(PTP_GENERAL_PORT); + +#ifdef PTPD_PCAP + if ((netPath->pcapGeneral != NULL) && (rtOpts->transport == IEEE_802_3)) { + ret = netSendPcapEther(buf, length, + &netPath->etherDest, + (struct ether_addr *)netPath->interfaceID, + netPath->pcapGeneral); + + if (ret <= 0) + DBG("Error sending ether multicast general message\n"); + else { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } + } else { +#endif + if(destinationAddress) { + + addr.sin_addr.s_addr = destinationAddress; + /* + * This function is used for PTP only anyway... + * If we're sending to a unicast address, set the UNICAST flag. + */ + *(char *)(buf + 6) |= PTP_UNICAST; + + ret = sendto(netPath->generalSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending unicast general message\n"); + else { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } + } else { + addr.sin_addr.s_addr = netPath->multicastAddr; + + /* Is TTL OK? */ + if(netPath->ttlGeneral != rtOpts->ttl) { + /* Try restoring TTL */ + if (netSetMulticastTTL(netPath->generalSock,rtOpts->ttl)) { + netPath->ttlGeneral = rtOpts->ttl; + } + } + + ret = sendto(netPath->generalSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending multicast general message\n"); + else { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } + } + +#ifdef PTPD_PCAP + } +#endif + return ret; +} + +ssize_t +netSendPeerGeneral(Octet * buf, UInteger16 length, NetPath * netPath, const RunTimeOpts *rtOpts, Integer32 dst) +{ + + ssize_t ret; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(PTP_GENERAL_PORT); + +#ifdef PTPD_PCAP + if ((netPath->pcapGeneral != NULL) && (rtOpts->transport == IEEE_802_3)) { + ret = netSendPcapEther(buf, length, + &netPath->peerEtherDest, + (struct ether_addr *)netPath->interfaceID, + netPath->pcapGeneral); + + if (ret <= 0) + DBG("error sending ether multicast general message\n"); + + } else if (dst) +#else + if (dst) +#endif + { + addr.sin_addr.s_addr = dst; + + /* + * This function is used for PTP only anyway... + * If we're sending to a unicast address, set the UNICAST flag. + */ + *(char *)(buf + 6) |= PTP_UNICAST; + + ret = sendto(netPath->generalSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending unicast peer general message\n"); + + } else { + addr.sin_addr.s_addr = netPath->peerMulticastAddr; + + /* is TTL already 1 ? */ + if(netPath->ttlGeneral != 1) { + /* Try setting TTL to 1 */ + if (netSetMulticastTTL(netPath->generalSock,1)) { + netPath->ttlGeneral = 1; + } + } + ret = sendto(netPath->generalSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending multicast peer general message\n"); + + } + + if (ret > 0) { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } + + return ret; + +} + +ssize_t +netSendPeerEvent(Octet * buf, UInteger16 length, NetPath * netPath, const RunTimeOpts *rtOpts, Integer32 dst, TimeInternal * tim) +{ + ssize_t ret; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(PTP_EVENT_PORT); + +#ifdef PTPD_PCAP + if ((netPath->pcapGeneral != NULL) && (rtOpts->transport == IEEE_802_3)) { + ret = netSendPcapEther(buf, length, + &netPath->peerEtherDest, + (struct ether_addr *)netPath->interfaceID, + netPath->pcapGeneral); + + if (ret <= 0) + DBG("error sending ether multicast general message\n"); + } else if (dst) +#else + if (dst) +#endif + { + addr.sin_addr.s_addr = dst; + + /* + * This function is used for PTP only anyway... + * If we're sending to a unicast address, set the UNICAST flag. + */ + *(char *)(buf + 6) |= PTP_UNICAST; + + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending unicast peer event message\n"); + +#ifndef SO_TIMESTAMPING + /* + * Need to forcibly loop back the packet since + * we are not using multicast. + */ + addr.sin_addr.s_addr = netPath->interfaceAddr.s_addr; + + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error looping back unicast peer event message\n"); +#else + +#ifdef PTPD_PCAP + if((netPath->pcapEvent == NULL) && !netPath->txTimestampFailure) { +#else + if(!netPath->txTimestampFailure) { +#endif /* PTPD_PCAP */ + if(!getTxTimestamp(netPath, tim)) { + netPath->txTimestampFailure = TRUE; + if (tim) { + clearTime(tim); + } + } + } + + if(netPath->txTimestampFailure) { + /* We've had a TX timestamp receipt timeout - falling back to packet looping */ + addr.sin_addr.s_addr = netPath->interfaceAddr.s_addr; + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error looping back unicast event message\n"); + } +#endif /* SO_TIMESTAMPING */ + + } else { + addr.sin_addr.s_addr = netPath->peerMulticastAddr; + + /* is TTL already 1 ? */ + if(netPath->ttlEvent != 1) { + /* Try setting TTL to 1 */ + if (netSetMulticastTTL(netPath->eventSock,1)) { + netPath->ttlEvent = 1; + } + } + ret = sendto(netPath->eventSock, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + DBG("Error sending multicast peer event message\n"); +#ifdef SO_TIMESTAMPING + if(!netPath->txTimestampFailure) { + if(!getTxTimestamp(netPath, tim)) { + if (tim) { + clearTime(tim); + } + + netPath->txTimestampFailure = TRUE; + + /* Try re-enabling MULTICAST_LOOP */ + netSetMulticastLoopback(netPath, TRUE); + } + } +#endif /* SO_TIMESTAMPING */ + } + + if (ret > 0) { + netPath->sentPackets++; + netPath->sentPacketsTotal++; + } + + return ret; +} + + + +/* + * refresh IGMP on a timeout + */ +/* + * @return TRUE if successful + */ +Boolean +netRefreshIGMP(NetPath * netPath, const RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + DBG("netRefreshIGMP\n"); + + if(netPath->joinedGeneral) { + netShutdownMulticastIPv4(netPath, netPath->multicastAddr); + netPath->multicastAddr = 0; + } + + if(netPath->joinedPeer) { + netShutdownMulticastIPv4(netPath, netPath->peerMulticastAddr); + netPath->peerMulticastAddr = 0; + } + + /* suspend process 100 milliseconds, to make sure the kernel sends the IGMP_leave properly */ + usleep(100*1000); + + if (!netInitMulticast(netPath, rtOpts)) { + return FALSE; + } + + return TRUE; +} diff --git a/src/ptpd/src/dep/ntpengine/ntp_isc_md5.c b/src/ptpd/src/dep/ntpengine/ntp_isc_md5.c new file mode 100644 index 0000000..e4e34ac --- /dev/null +++ b/src/ptpd/src/dep/ntpengine/ntp_isc_md5.c @@ -0,0 +1,273 @@ +/* + * This file contains the minimal set of declarations and constants + * extracted from the ISC MD5 code and ntpdc code included with NTP 4 + * distribution version 4.2.6p5 to allow computing the MD5 digest + * for NTP type 7 packets + */ + +/* Original copyright notice */ + +/* + * Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2000, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../../ptpd.h" + +#include "ntp_isc_md5.h" + +void ntp_memset (char *, int, int); + +#define memcmp(a, b, c) bcmp(a, b, (int)(c)) +#define memmove(t, f, c) bcopy(f, t, (int)(c)) +#define memcpy(t, f, c) bcopy(f, t, (int)(c)) +#define memset(a, x, c) if (0 == (x)) \ + bzero(a, (int)(c)); \ + else \ + ntp_memset((char *)(a), x, c) + +static void +byteSwap(uint32_t *buf, unsigned words) +{ + unsigned char *p = (unsigned char *)buf; + + do { + *buf++ = (uint32_t)((unsigned)p[3] << 8 | p[2]) << 16 | + ((unsigned)p[1] << 8 | p[0]); + p += 4; + } while (--words); +} + +/*! + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +isc_md5_init(isc_md5_t *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +void +isc_md5_invalidate(isc_md5_t *ctx) { + memset(ctx, 0, sizeof(isc_md5_t)); +} + +/*@{*/ +/*! The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) +/*@}*/ + +/*! This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + in, w = (w<>(32-s)) + x) + +/*! + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +transform(uint32_t buf[4], uint32_t const in[16]) { + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +/*! + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +isc_md5_update(isc_md5_t *ctx, const unsigned char *buf, unsigned int len) { + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((unsigned char *)ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((unsigned char *)ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/*! + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +isc_md5_final(isc_md5_t *ctx, unsigned char *digest) { + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + unsigned char *p = (unsigned char *)ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + transform(ctx->buf, ctx->in); + p = (unsigned char *)ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(isc_md5_t)); /* In case it's sensitive */ +} + +/* + * MD5authencrypt - generate message digest + * + * Returns length of MAC including key ID and digest. + */ + +int +MD5authencrypt( + char *key, /* key pointer */ + uint32_t *pkt, /* packet pointer */ + int length, + keyid_t keyid + ) +{ + u_char digest[64]; + u_int len; + PTPD_EVP_MD_CTX ctx; + pkt[length / 4] = htonl(keyid); + EVP_DigestInit(&ctx); + EVP_DigestUpdate(&ctx, (u_char *)key, (u_int)strlen(key)); + EVP_DigestUpdate(&ctx, (u_char *)pkt, (u_int)length); + EVP_DigestFinal(&ctx, digest, &len); + memmove((u_char *)pkt + length + 4, digest, len); + return (len + 4); +} diff --git a/src/ptpd/src/dep/ntpengine/ntp_isc_md5.h b/src/ptpd/src/dep/ntpengine/ntp_isc_md5.h new file mode 100644 index 0000000..236c0e4 --- /dev/null +++ b/src/ptpd/src/dep/ntpengine/ntp_isc_md5.h @@ -0,0 +1,99 @@ +/* + * This file contains the minimal set of declarations and constants + * extracted from the ISC MD5 code included with NTP 4 distribution + * version 4.2.6p5 to allow computing the MAC for NTP type 7 packets + */ + +/* Original copyright notice */ + +/* + * Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2000, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: md5.h,v 1.16 2007/06/19 23:47:18 tbox Exp $ */ + +/*! \file isc/md5.h + * \brief This is the header file for the MD5 message-digest algorithm. + * + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' + * header definitions; now uses stuff from dpkg's config.h + * - Ian Jackson . + * Still in the public domain. + */ + +#ifndef ISC_MD5_H +#define ISC_MD5_H 1 + +#ifdef HAVE_STRINGS_H +#include +#endif /* HAVE_STRINGS_H */ + +#define ISC_MD5_DIGESTLENGTH 16U + + +typedef struct { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +} isc_md5_t; + +void +isc_md5_init(isc_md5_t *ctx); + +void +isc_md5_invalidate(isc_md5_t *ctx); + +void +isc_md5_update(isc_md5_t *ctx, const unsigned char *buf, unsigned int len); + +void +isc_md5_final(isc_md5_t *ctx, unsigned char *digest); + + typedef isc_md5_t MD5_CTX; +# define MD5Init(c) isc_md5_init(c) +# define MD5Update(c, p, s) isc_md5_update(c, p, s) +# define MD5Final(d, c) isc_md5_final((c), (d)) /* swapped */ + typedef MD5_CTX PTPD_EVP_MD_CTX; +# define EVP_DigestInit(c) MD5Init(c) +# define EVP_DigestUpdate(c, p, s) MD5Update(c, p, s) +# define EVP_DigestFinal(c, d, pdl) \ + do { \ + MD5Final((d), (c)); \ + *(pdl) = 16; \ + } while (0) + +#define NID_md5 4 +#define JAN_1970 2208988800UL +#define FRAC 4294967296LL + +int MD5authencrypt( char *key, uint32_t *pkt, int length, keyid_t keyid ); + +#endif /* ISC_MD5_H */ + diff --git a/src/ptpd/src/dep/ntpengine/ntpdcontrol.c b/src/ptpd/src/dep/ntpengine/ntpdcontrol.c new file mode 100644 index 0000000..0c83adb --- /dev/null +++ b/src/ptpd/src/dep/ntpengine/ntpdcontrol.c @@ -0,0 +1,874 @@ +/*- + * Copyright (c) 2013-2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ntpdcontrol.c + * @date Tue Jul 20 22:19:20 2013 + * + * @brief Functions allowing remote control of the ntpd daemon + * using mode 7 packets + * + */ + +/* + * This code is largely based on the source code of the ntpdc utility from the + * NTP distribution version 4.2.6p5 and is mostly a bridge between PTPd2 APIs + * and NTPDc code - functionality so far is limited to setting and clearing system + * flags to allow failover to- and -from (local) NTP + */ + +/* Original NTP4 copyright notice */ + +/*********************************************************************** + * * + * Copyright (c) University of Delaware 1992-2011 * + * * + * Permission to use, copy, modify, and distribute this software and * + * its documentation for any purpose with or without fee is hereby * + * granted, provided that the above copyright notice appears in all * + * copies and that both the copyright notice and this permission * + * notice appear in supporting documentation, and that the name * + * University of Delaware not be used in advertising or publicity * + * pertaining to distribution of the software without specific, * + * written prior permission. The University of Delaware makes no * + * representations about the suitability this software for any * + * purpose. It is provided "as is" without express or implied * + * warranty. * + * * + ***********************************************************************/ + +#include "../../ptpd.h" + +#define NTP_PORT 123 + +char *ntpdc_pktdata; + +Boolean +ntpInit(NTPoptions* options, NTPcontrol* control) +{ + + int res = TRUE; + TimingService service = control->timingService; + + control->sockFD = -1; + if(!options->enableEngine) + return FALSE; + + memset(control, 0, sizeof(*control)); + /* preserve TimingService... temporary */ + control->timingService = service; + + if(!hostLookup(options->hostAddress, &control->serverAddress)) { + control->serverAddress = 0; + return FALSE; + } + + if ((control->sockFD = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 ) { + PERROR("failed to initalize NTP control socket"); + return FALSE; + } + + /* This will attempt to read the ntpd control flags for the first time */ + res = ntpdInControl(options, control); + + if (res != INFO_YES && res != INFO_NO) { + return FALSE; + } + + DBGV("NTPd original flags: %d\n", control->originalFlags); + return TRUE; +} + +Boolean +ntpShutdown(NTPoptions* options, NTPcontrol* control) +{ + + /* Attempt reverting ntpd flags to the original value */ + if(control->flagsCaptured) { + /* we only control the kernel and ntp flags */ + /* just to avoid -Wunused* */ +#ifdef RUNTIME_DEBUG + int resC = ntpdClearFlags(options, control, ~(control->originalFlags) & (INFO_FLAG_KERNEL | INFO_FLAG_NTP)); + int resS = ntpdSetFlags(options, control, control->originalFlags & (INFO_FLAG_KERNEL | INFO_FLAG_NTP)); + DBGV("Attempting to revert NTPd flags to %d - result: clear %d set %d\n", control->originalFlags, + resC, resS); +#else + ntpdClearFlags(options, control, ~(control->originalFlags) & (INFO_FLAG_KERNEL | INFO_FLAG_NTP)); + ntpdSetFlags(options, control, control->originalFlags & (INFO_FLAG_KERNEL | INFO_FLAG_NTP)); +#endif /* RUNTIME_DEBUG */ + } + + if (control->sockFD > 0) + close(control->sockFD); + control->sockFD = -1; + + return TRUE; +} + + +static ssize_t +ntpSend(NTPcontrol* control, Octet * buf, UInteger16 length) +{ + ssize_t ret = -1; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(NTP_PORT); + + if (control->serverAddress) { + + addr.sin_addr.s_addr = control->serverAddress; + + ret = sendto(control->sockFD, buf, length, 0, + (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + if (ret <= 0) + INFO("error sending NTP control message\n"); + + } + return ret; +} + + +/* + * Default values we use + */ +#define DEFHOST "localhost" /* default host name */ +#define DEFTIMEOUT (5) /* 5 second time out */ +#define DEFSTIMEOUT (2) /* 2 second time out after first */ +#define DEFDELAY 0x51EB852 /* 20 milliseconds, l_fp fraction */ +#define LENHOSTNAME 256 /* host name is 256 characters long */ +#define MAXCMDS 100 /* maximum commands on cmd line */ +#define MAXHOSTS 200 /* maximum hosts on cmd line */ +#define MAXLINE 512 /* maximum line length */ +#define MAXTOKENS (1+1+MAXARGS+MOREARGS+2) /* maximum number of usable tokens */ +#define SCREENWIDTH 78 /* nominal screen width in columns */ + +#define INITDATASIZE (sizeof(struct resp_pkt) * 16) +#define INCDATASIZE (sizeof(struct resp_pkt) * 8) + +/* + * These are used to help the magic with old and new versions of ntpd. + */ +#define IMPL_XNTPD 3 +int impl_ver = IMPL_XNTPD; +#define REQ_LEN_NOMAC (offsetof(struct req_pkt, keyid)) +static int req_pkt_size = REQ_LEN_NOMAC; +#define ERR_INCOMPLETE 16 +#define ERR_TIMEOUT 17 + +static void +get_systime( + l_fp *now /* system time */ + ) +{ + double dtemp; + +#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_GETCLOCK) + struct timespec ts; /* seconds and nanoseconds */ + + /* + * Convert Unix clock from seconds and nanoseconds to seconds. + */ +# ifdef HAVE_CLOCK_GETTIME + clock_gettime(CLOCK_REALTIME, &ts); +# else + getclock(TIMEOFDAY, &ts); +# endif + now->l_i = ts.tv_sec + JAN_1970; + dtemp = ts.tv_nsec / 1e9; + +#else /* HAVE_CLOCK_GETTIME || HAVE_GETCLOCK */ + struct timeval tv; /* seconds and microseconds */ + + /* + * Convert Unix clock from seconds and microseconds to seconds. + */ + gettimeofday(&tv, NULL); + now->l_i = tv.tv_sec + JAN_1970; + dtemp = tv.tv_usec / 1e6; + +#endif /* HAVE_CLOCK_GETTIME || HAVE_GETCLOCK */ + + /* + * Renormalize to seconds past 1900 and fraction. + */ + +// dtemp += sys_residual; + if (dtemp >= 1) { + dtemp -= 1; + now->l_i++; + } else if (dtemp < -1) { + dtemp += 1; + now->l_i--; + } + dtemp *= FRAC; + now->l_uf = (uint32_t)dtemp; +} + +static int +NTPDCrequest( + NTPoptions* options, + NTPcontrol* control, + int reqcode, + int auth, + u_int qitems, + size_t qsize, + char *qdata + ) +{ + + l_fp delay_time = { .l_ui=0, .l_uf=0x51EB852}; + + struct req_pkt qpkt; + size_t datasize; + size_t reqsize; + l_fp ts; + l_fp * ptstamp; + int maclen; + static char *key; + + key=calloc(21,sizeof(char)); + strncpy(key,options->key,20); + + memset(&qpkt, 0, sizeof(qpkt)); + qpkt.rm_vn_mode = RM_VN_MODE(0, 0, 0); + qpkt.implementation = (u_char)3; + qpkt.request = (u_char)reqcode; + datasize = qitems * qsize; + if (datasize && qdata != NULL) { + memcpy(qpkt.data, qdata, datasize); + qpkt.err_nitems = ERR_NITEMS(0, qitems); + qpkt.mbz_itemsize = MBZ_ITEMSIZE(qsize); + } else { + qpkt.err_nitems = ERR_NITEMS(0, 0); + qpkt.mbz_itemsize = MBZ_ITEMSIZE(qsize); /* allow for optional first item */ + } + + if (!auth) { + qpkt.auth_seq = AUTH_SEQ(0, 0); + return ntpSend(control, (Octet *)&qpkt, req_pkt_size); + } + + qpkt.auth_seq = AUTH_SEQ(1, 0); + + reqsize = req_pkt_size; + + ptstamp = (void *)((u_char *)&qpkt + reqsize); + ptstamp--; + get_systime(&ts); + L_ADD(&ts, &delay_time); + HTONL_FP(&ts, ptstamp); + + maclen = MD5authencrypt(key, (void *)&qpkt, reqsize,options->keyId); + free(key); + if (!maclen || (maclen != (16 + sizeof(keyid_t)))) + { + ERROR("Error while computing NTP MD5 hash\n"); + return 1; + } + + return ntpSend(control, (Octet *)&qpkt, reqsize + maclen); + +} + + +/* + * checkitems - utility to print a message if no items were returned + */ +/* +static int +checkitems( + int items + + ) +{ + if (items == 0) { + + return 0; + } + return 1; +} +*/ + +/* + * checkitemsize - utility to print a message if the item size is wrong + */ +static int +checkitemsize( + int itemsize, + int expected + ) +{ + if (itemsize != expected) { + return 0; + } + return 1; +} + + +/* + * check1item - check to make sure we have exactly one item + */ +static int +check1item( + int items + + ) +{ + if (items == 0) { + return 0; + } + if (items > 1) { + + return 0; + } + return 1; +} + +static int +NTPDCresponse( + NTPoptions* options, + NTPcontrol* control, + int reqcode, + int *ritems, + int *rsize, + char **rdata, + int esize + ) +{ + struct resp_pkt rpkt; + struct timeval tvo; + int items; + int i; + int size; + int datasize; + char *datap; + char *tmp_data; + char haveseq[MAXSEQ+1]; + int firstpkt; + int lastseq; + int numrecv; + int seq; + fd_set fds; + int n; + int pad; + int retries = NTP_EINTR_RETRIES; + + int ntpdc_pktdatasize; + + int implcode=(u_char)3; + + static struct timeval tvout = { DEFTIMEOUT, 0 }; /* time out for reads */ + static struct timeval tvsout = { DEFSTIMEOUT, 0 }; /* secondary time out */ + +// static char *currenthost = "localhost"; + + ntpdc_pktdatasize = INITDATASIZE; + ntpdc_pktdata = realloc(ntpdc_pktdata,INITDATASIZE); + + /* + * This is pretty tricky. We may get between 1 and many packets + * back in response to the request. We peel the data out of + * each packet and collect it in one long block. When the last + * packet in the sequence is received we'll know how many we + * should have had. Note we use one long time out, should reconsider. + */ + *ritems = 0; + *rsize = 0; + firstpkt = 1; + numrecv = 0; + + lastseq = 999; /* too big to be a sequence number */ + memset(haveseq, 0, sizeof(haveseq)); + + + again: + if (firstpkt) + tvo = tvout; + else + tvo = tvsout; + do { + FD_ZERO(&fds); + FD_SET(control->sockFD, &fds); + n = select(control->sockFD+1, &fds, (fd_set *)0, (fd_set *)0, &tvo); + if(n == -1) { + if(errno == EINTR) { + DBG("NTPDCresponse(): EINTR caught\n"); + retries--; + } else { + retries = 0; + } + } + } while ((n == -1) && retries); + + if (n == -1) { + DBG("NTPDCresponse(): select failed - not EINTR: %s\n", strerror(errno)); + return -1; + } + if (n == 0) { + DBG("NTP response select timeout"); + if (firstpkt) { + return ERR_TIMEOUT; + } else { + return ERR_INCOMPLETE; + } + } + + n = recv(control->sockFD, (char *)&rpkt, sizeof(rpkt), 0); + + if (n == -1) { + DBG("NTP response recv failed\n"); + return -1; + } + + + /* + * Check for format errors. Bug proofing. + */ + if (n < RESP_HEADER_SIZE) { + goto again; + } + + + + if (INFO_VERSION(rpkt.rm_vn_mode) > NTP_VERSION || + INFO_VERSION(rpkt.rm_vn_mode) < NTP_OLDVERSION) { +/* if (debug) + printf("Packet received with version %d\n", + INFO_VERSION(rpkt.rm_vn_mode)); +*/ + + goto again; + } + + if (INFO_MODE(rpkt.rm_vn_mode) != MODE_PRIVATE) { +/* if (debug) + printf("Packet received with mode %d\n", + INFO_MODE(rpkt.rm_vn_mode)); +*/ + + goto again; + + } + if (INFO_IS_AUTH(rpkt.auth_seq)) { +/* + if (debug) + printf("Encrypted packet received\n"); +*/ + goto again; + } + if (!ISRESPONSE(rpkt.rm_vn_mode)) { +/* + if (debug) + printf("Received request packet, wanted response\n"); +*/ + goto again; + } + if (INFO_MBZ(rpkt.mbz_itemsize) != 0) { +/* + if (debug) + printf("Received packet with nonzero MBZ field!\n"); +*/ + goto again; + } + + /* + * Check implementation/request. Could be old data getting to us. + */ + + if (rpkt.implementation != implcode || rpkt.request != reqcode) { +/* + if (debug) + printf( + "Received implementation/request of %d/%d, wanted %d/%d", + rpkt.implementation, rpkt.request, + implcode, reqcode); +*/ + goto again; + } + + /* + * Check the error code. If non-zero, return it. + */ + if (INFO_ERR(rpkt.err_nitems) != INFO_OKAY) { +/* + if (debug && ISMORE(rpkt.rm_vn_mode)) { + printf("Error code %d received on not-final packet\n", + INFO_ERR(rpkt.err_nitems)); + } +*/ + return (int)INFO_ERR(rpkt.err_nitems); + } + + /* + * Collect items and size. Make sure they make sense. + */ + items = INFO_NITEMS(rpkt.err_nitems); + + size = INFO_ITEMSIZE(rpkt.mbz_itemsize); + if (esize > size) + pad = esize - size; + else + pad = 0; + datasize = items * size; + + if ((size_t)datasize > (n-RESP_HEADER_SIZE)) { +/* if (debug) + printf( + "Received items %d, size %d (total %d), data in packet is %lu\n", + items, size, datasize, (u_long)(n-RESP_HEADER_SIZE)); +*/ + goto again; + + } + + /* + * If this isn't our first packet, make sure the size matches + * the other ones. + */ + if (!firstpkt && esize != *rsize) { +/* if (debug) + printf("Received itemsize %d, previous %d\n", + size, *rsize); +*/ + goto again; + } + /* + * If we've received this before, +toss it + */ + seq = INFO_SEQ(rpkt.auth_seq); + if (haveseq[seq]) { +/* if (debug) + printf("Received duplicate sequence number %d\n", seq); +*/ + goto again; + } + haveseq[seq] = 1; + + /* + * If this is the last in the sequence, record that. + */ + if (!ISMORE(rpkt.rm_vn_mode)) { + if (lastseq != 999) { + DBGV("NTPDC Received second end sequence packet\n"); + goto again; + } + lastseq = seq; + } + + *rdata = datap = ntpdc_pktdata; + + /* + * So far, so good. Copy this data into the output array. + */ + if ((datap + datasize + (pad * items)) > (ntpdc_pktdata + ntpdc_pktdatasize)) { + int offset = datap - ntpdc_pktdata; + + ntpdc_pktdatasize += INCDATASIZE; + ntpdc_pktdata = realloc(ntpdc_pktdata, (size_t)ntpdc_pktdatasize); + *rdata = ntpdc_pktdata; /* might have been realloced ! */ + datap = ntpdc_pktdata + offset; + } + /* + * We now move the pointer along according to size and number of + * items. This is so we can play nice with older implementations + */ + + tmp_data = rpkt.data; + for (i = 0; i < items; i++) { + memcpy(datap, tmp_data, (unsigned)size); + tmp_data += size; + memset(datap + size, 0, pad); + datap += size + pad; + } + + if (firstpkt) { + firstpkt = 0; + *rsize = size + pad; + } + *ritems += items; + + /* + * Finally, check the count of received packets. If we've got them + * all, return + */ + ++numrecv; + + if (numrecv <= lastseq) + goto again; + + return INFO_OKAY; +} + +static int +NTPDCquery( + NTPoptions* options, + NTPcontrol* control, + int reqcode, + int auth, + int qitems, + int qsize, + char *qdata, + int *ritems, + int *rsize, + char **rdata, + int quiet_mask, + int esize + ) +{ + int res; + int retries = NTP_EINTR_RETRIES; + char junk[512]; + fd_set fds; + struct timeval tvzero; +// int implcode=(u_char)3; + + /* + * Poll the socket and clear out any pending data + */ +again: + do { + tvzero.tv_sec = tvzero.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(control->sockFD, &fds); + res = select(control->sockFD+1, &fds, (fd_set *)0, (fd_set *)0, &tvzero); + + if (res == -1) { + if((errno == EINTR) && retries ) { + DBG("NTPDCquery(): select EINTR caught"); + retries--; + res = 1; + continue; + } + DBG("NTPDCquery(): select() error - not EINTR: %s\n", strerror(errno)); + return -1; + } else if (res > 0) { + (void) recv(control->sockFD, junk, sizeof junk, 0); + } + } while (res > 0); + + /* + * send a request + */ + res = NTPDCrequest(options, control, reqcode, auth, qitems, qsize, qdata); + if (res <= 0) { + return res; + } + /* + * Get the response. If we got a standard error, print a message + */ + res = NTPDCresponse(options, control, reqcode, ritems, rsize, rdata, esize); + + /* + * Try to be compatible with older implementations of ntpd. + */ + if (res == INFO_ERR_FMT && req_pkt_size != 48) { +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + int oldsize = req_pkt_size; +#endif /* RUNTIME_DEBUG */ + + switch(req_pkt_size) { + case REQ_LEN_NOMAC: + req_pkt_size = 160; + break; + case 160: + req_pkt_size = 48; + break; + } + if (impl_ver == IMPL_XNTPD) { + DBGV( + "NTPDC ***Warning changing to older implementation\n"); + return INFO_ERR_IMPL; + } + + DBGV( + "NTPDC ***Warning changing the request packet size from %d to %d\n", + oldsize, req_pkt_size); + goto again; + } + + return res; +} + +int +ntpdControlFlags(NTPoptions* options, NTPcontrol* control, int req, int flags) +{ + struct conf_sys_flags sys; + int items; + int itemsize; + char *dummy; + int res; + + sys.flags = 0; + res = 0; + sys.flags = flags; + + sys.flags = htonl(sys.flags); + + res = NTPDCquery(options, control, req, 1, 1, + sizeof(struct conf_sys_flags), (char *)&sys, &items, + &itemsize, &dummy, 0, sizeof(struct conf_sys_flags)); + + if ((res != INFO_OKAY) && (sys.flags == 0)) + return 0; + + if (res != INFO_OKAY) { + + switch (res) { + + case -1: + if(!control->requestFailed) ERROR("Cannot connect to NTP daemon\n"); + break; + + case INFO_ERR_AUTH: + + if(!control->requestFailed) ERROR("NTP permission denied: check key id, password and NTP configuration\n"); + break; + + case ERR_TIMEOUT: + + if(!control->requestFailed) ERROR("Timeout while connecting to NTP daemon\n"); + break; + + default: + ERROR("NTP protocol error\n"); + + } + } + + return res; + +} + +int +ntpdSetFlags(NTPoptions* options, NTPcontrol* control, int flags) +{ + + int res; + ntpdc_pktdata = malloc(INITDATASIZE); + DBGV("Setting NTP flags %d\n", flags); + res=ntpdControlFlags(options, control, REQ_SET_SYS_FLAG, flags); + free(ntpdc_pktdata); + return res; + +} + +int +ntpdClearFlags(NTPoptions* options, NTPcontrol* control, int flags) +{ + + int res; + ntpdc_pktdata = malloc(INITDATASIZE); + DBGV("Clearing NTP flags %d\n", flags); + res=ntpdControlFlags(options, control, REQ_CLR_SYS_FLAG, flags); + free(ntpdc_pktdata); + return res; +} + + +int +ntpdInControl(NTPoptions* options, NTPcontrol* control) +{ + struct info_sys *is; + int items; + int itemsize; + int res; + ntpdc_pktdata = malloc(INITDATASIZE); + res = NTPDCquery(options, control, REQ_SYS_INFO, 0, 0, 0, (char *)NULL, + &items, &itemsize, (void *)&is, 0, + sizeof(struct info_sys)); + + if ( res != 0 ) + goto end; + + if (!check1item(items)) { + + res=INFO_ERR_EMPTY; + goto end; + } + + + if (!checkitemsize(itemsize, sizeof(struct info_sys)) && + !checkitemsize(itemsize, v4sizeof(struct info_sys))) { + + res=INFO_ERR_EMPTY; + goto end; + } + + if (is->flags & INFO_FLAG_NTP) DBGV("NTP flag seen: ntp\n"); + if (is->flags & INFO_FLAG_KERNEL) DBGV("NTP flag seen: kernel\n"); + + if(!control->flagsCaptured) { + control->originalFlags = is->flags; + /* we only control the kernel and ntp flags */ + control->originalFlags &= (INFO_FLAG_KERNEL | INFO_FLAG_NTP); + control->flagsCaptured = TRUE; + res = INFO_YES; + goto end; + } + + if ((is->flags & INFO_FLAG_NTP) || (is->flags & INFO_FLAG_KERNEL)) + { + res=INFO_YES; + } else { + + res=INFO_NO; + } + + end: + + free(ntpdc_pktdata); + + + if (res != INFO_YES && res != INFO_NO) { + + switch (res) { + + case -1: + DBG("Could not connect to NTP daemon\n"); + break; + + case ERR_TIMEOUT: + + DBG("Timeout while connecting to NTP daemon\n"); + break; + + case INFO_ERR_AUTH: + + DBG("NTP permission denied: check NTP key id, key and if key is trusted and is a request key\n"); + break; + + default: + ERROR("NTP protocol error\n"); + + } + } + return res; + +} + diff --git a/src/ptpd/src/dep/ntpengine/ntpdcontrol.h b/src/ptpd/src/dep/ntpengine/ntpdcontrol.h new file mode 100644 index 0000000..fde9707 --- /dev/null +++ b/src/ptpd/src/dep/ntpengine/ntpdcontrol.h @@ -0,0 +1,362 @@ +/* + * ntpdcontrol.h - definitions for the ntpd remote query and control facility + */ + +#ifndef NTPDCONTROL_H +#define NTPDCONTROL_H + +#include "../../ptpd.h" + +#include "../../timingdomain.h" + +typedef struct { + + Boolean enableEngine; + Boolean enableControl; + Boolean enableFailover; + int failoverTimeout; + int checkInterval; + int keyId; + char key[20]; + Octet hostAddress[MAXHOSTNAMELEN]; +} NTPoptions; + +typedef struct { + Boolean operational; + Boolean enabled; + Boolean isRequired; + Boolean inControl; + Boolean isFailOver; + Boolean checkFailed; + Boolean requestFailed; + Boolean flagsCaptured; + int originalFlags; + Integer32 serverAddress; + Integer32 sockFD; + struct TimingService timingService; +} NTPcontrol; + +Boolean ntpInit(NTPoptions* options, NTPcontrol* control); +Boolean ntpShutdown(NTPoptions* options, NTPcontrol* control); +int ntpdControlFlags(NTPoptions* options, NTPcontrol* control, int req, int flags); +int ntpdSetFlags(NTPoptions* options, NTPcontrol* control, int flags); +int ntpdClearFlags(NTPoptions* options, NTPcontrol* control, int flags); +int ntpdInControl(NTPoptions* options, NTPcontrol* control); +//Boolean ntpdControl(NTPoptions* options, NTPcontrol* control, Boolean quiet); + + +#define NTPCONTROL_YES 128 +#define NTPCONTROL_NO 129 +#define NTPCONTROL_AUTHERR 18 +#define NTPCONTROL_TIMEOUT 19 +#define NTPCONTROL_PROTOERR 20 +#define NTPCONTROL_NETERR 21 + + +typedef struct { + union { + uint32_t Xl_ui; + int32_t Xl_i; + } Ul_i; + union { + uint32_t Xl_uf; + int32_t Xl_f; + } Ul_f; +} l_fp; + +#define l_ui Ul_i.Xl_ui /* unsigned integral part */ +#define l_i Ul_i.Xl_i /* signed integral part */ +#define l_uf Ul_f.Xl_uf /* unsigned fractional part */ +#define l_f Ul_f.Xl_f /* signed fractional part */ + +#define M_ADD(r_i, r_f, a_i, a_f) /* r += a */ \ + do { \ + register uint32_t lo_tmp; \ + register uint32_t hi_tmp; \ + \ + lo_tmp = ((r_f) & 0xffff) + ((a_f) & 0xffff); \ + hi_tmp = (((r_f) >> 16) & 0xffff) + (((a_f) >> 16) & 0xffff); \ + if (lo_tmp & 0x10000) \ + hi_tmp++; \ + (r_f) = ((hi_tmp & 0xffff) << 16) | (lo_tmp & 0xffff); \ + \ + (r_i) += (a_i); \ + if (hi_tmp & 0x10000) \ + (r_i)++; \ + } while (0) + + +#define L_ADD(r, a) M_ADD((r)->l_ui, (r)->l_uf, (a)->l_ui, (a)->l_uf) + +#define HTONL_FP(h, n) do { (n)->l_ui = htonl((h)->l_ui); \ + (n)->l_uf = htonl((h)->l_uf); } while (0) + +typedef unsigned short associd_t; /* association ID */ +typedef int32_t keyid_t; /* cryptographic key ID */ +typedef uint32_t tstamp_t; /* NTP seconds timestamp */ + + +typedef int32_t s_fp; +typedef uint32_t u_fp; +typedef char s_char; +#define MAX_MAC_LEN (6 * sizeof(uint32_t)) /* SHA */ + +/* + * A request packet. These are almost a fixed length. + */ +struct req_pkt { + u_char rm_vn_mode; /* response, more, version, mode */ + u_char auth_seq; /* key, sequence number */ + u_char implementation; /* implementation number */ + u_char request; /* request number */ + u_short err_nitems; /* error code/number of data items */ + u_short mbz_itemsize; /* item size */ + char data[128 + 48]; /* data area [32 prev](176 byte max) */ + /* struct conf_peer must fit */ + l_fp tstamp; /* time stamp, for authentication */ + keyid_t keyid; /* (optional) encryption key */ + char mac[MAX_MAC_LEN-sizeof(keyid_t)]; /* (optional) auth code */ +}; + +/* + * The req_pkt_tail structure is used by ntpd to adjust for different + * packet sizes that may arrive. + */ +struct req_pkt_tail { + l_fp tstamp; /* time stamp, for authentication */ + keyid_t keyid; /* (optional) encryption key */ + char mac[MAX_MAC_LEN-sizeof(keyid_t)]; /* (optional) auth code */ +}; + +/* MODE_PRIVATE request packet header length before optional items. */ +#define REQ_LEN_HDR (offsetof(struct req_pkt, data)) +/* MODE_PRIVATE request packet fixed length without MAC. */ +#define REQ_LEN_NOMAC (offsetof(struct req_pkt, keyid)) +/* MODE_PRIVATE req_pkt_tail minimum size (16 octet digest) */ +#define REQ_TAIL_MIN \ + (sizeof(struct req_pkt_tail) - (MAX_MAC_LEN - MAX_MD5_LEN)) + +/* + * A MODE_PRIVATE response packet. The length here is variable, this + * is a maximally sized one. Note that this implementation doesn't + * authenticate responses. + */ +#define RESP_HEADER_SIZE (offsetof(struct resp_pkt, data)) +#define RESP_DATA_SIZE (500) + +struct resp_pkt { + u_char rm_vn_mode; /* response, more, version, mode */ + u_char auth_seq; /* key, sequence number */ + u_char implementation; /* implementation number */ + u_char request; /* request number */ + u_short err_nitems; /* error code/number of data items */ + u_short mbz_itemsize; /* item size */ + char data[RESP_DATA_SIZE]; /* data area */ +}; + + +/* + * Information error codes + */ +#define INFO_OKAY 0 +#define INFO_ERR_IMPL 1 /* incompatable implementation */ +#define INFO_ERR_REQ 2 /* unknown request code */ +#define INFO_ERR_FMT 3 /* format error */ +#define INFO_ERR_NODATA 4 /* no data for this request */ +#define INFO_ERR_AUTH 7 /* authentication failure */ +#define INFO_ERR_EMPTY 8 /* wowczarek: problem with response items */ +#define INFO_YES 126 /* wowczarek: NTP is controlling the OS clock */ +#define INFO_NO 127 /* wowczarek: NTP is not controlling the OS clock */ +/* + * Maximum sequence number. + */ +#define MAXSEQ 127 + +/* + * Bit setting macros for multifield items. + */ +#define RESP_BIT 0x80 +#define MORE_BIT 0x40 + +#define ISRESPONSE(rm_vn_mode) (((rm_vn_mode)&RESP_BIT)!=0) +#define ISMORE(rm_vn_mode) (((rm_vn_mode)&MORE_BIT)!=0) +#define INFO_VERSION(rm_vn_mode) ((u_char)(((rm_vn_mode)>>3)&0x7)) +#define INFO_MODE(rm_vn_mode) ((rm_vn_mode)&0x7) + +#define RM_VN_MODE(resp, more, version) \ + ((u_char)(((resp)?RESP_BIT:0)\ + |((more)?MORE_BIT:0)\ + |((version?version:(NTP_OLDVERSION+1))<<3)\ + |(MODE_PRIVATE))) + +#define INFO_IS_AUTH(auth_seq) (((auth_seq) & 0x80) != 0) +#define INFO_SEQ(auth_seq) ((auth_seq)&0x7f) +#define AUTH_SEQ(auth, seq) ((u_char)((((auth)!=0)?0x80:0)|((seq)&0x7f))) + +#define INFO_ERR(err_nitems) ((u_short)((ntohs(err_nitems)>>12)&0xf)) +#define INFO_NITEMS(err_nitems) ((u_short)(ntohs(err_nitems)&0xfff)) +#define ERR_NITEMS(err, nitems) (htons((u_short)((((u_short)(err)<<12)&0xf000)\ + |((u_short)(nitems)&0xfff)))) + +#define INFO_MBZ(mbz_itemsize) ((ntohs(mbz_itemsize)>>12)&0xf) +#define INFO_ITEMSIZE(mbz_itemsize) ((u_short)(ntohs(mbz_itemsize)&0xfff)) +#define MBZ_ITEMSIZE(itemsize) (htons((u_short)(itemsize))) + + +/* + * Implementation numbers. One for universal use and one for ntpd. + */ +#define IMPL_UNIV 0 +#define IMPL_XNTPD_OLD 2 /* Used by pre ipv6 ntpdc */ +#define IMPL_XNTPD 3 /* Used by post ipv6 ntpdc */ + +/* + * Some limits related to authentication. Frames which are + * authenticated must include a time stamp which differs from + * the receive time stamp by no more than 10 seconds. + */ +#define INFO_TS_MAXSKEW 10. +#define NTP_OLDVERSION ((u_char)1) +#define MODE_PRIVATE 7 +/* + * Universal request codes go here. There aren't any. + */ + +/* + * NTPD request codes go here. + */ +#define REQ_PEER_LIST 0 /* return list of peers */ +#define REQ_PEER_LIST_SUM 1 /* return summary info for all peers */ +#define REQ_PEER_INFO 2 /* get standard information on peer */ +#define REQ_PEER_STATS 3 /* get statistics for peer */ +#define REQ_SYS_INFO 4 /* get system information */ +#define REQ_SYS_STATS 5 /* get system stats */ +#define REQ_IO_STATS 6 /* get I/O stats */ +#define REQ_MEM_STATS 7 /* stats related to peer list maint */ +#define REQ_LOOP_INFO 8 /* info from the loop filter */ +#define REQ_TIMER_STATS 9 /* get timer stats */ +#define REQ_CONFIG 10 /* configure a new peer */ +#define REQ_UNCONFIG 11 /* unconfigure an existing peer */ +#define REQ_SET_SYS_FLAG 12 /* set system flags */ +#define REQ_CLR_SYS_FLAG 13 /* clear system flags */ +#define REQ_MONITOR 14 /* (not used) */ +#define REQ_NOMONITOR 15 /* (not used) */ +#define REQ_GET_RESTRICT 16 /* return restrict list */ +#define REQ_RESADDFLAGS 17 /* add flags to restrict list */ +#define REQ_RESSUBFLAGS 18 /* remove flags from restrict list */ +#define REQ_UNRESTRICT 19 /* remove entry from restrict list */ +#define REQ_MON_GETLIST 20 /* return data collected by monitor */ +#define REQ_RESET_STATS 21 /* reset stat counters */ +#define REQ_RESET_PEER 22 /* reset peer stat counters */ +#define REQ_REREAD_KEYS 23 /* reread the encryption key file */ +#define REQ_DO_DIRTY_HACK 24 /* (not used) */ +#define REQ_DONT_DIRTY_HACK 25 /* (not used) */ +#define REQ_TRUSTKEY 26 /* add a trusted key */ +#define REQ_UNTRUSTKEY 27 /* remove a trusted key */ +#define REQ_AUTHINFO 28 /* return authentication info */ +#define REQ_TRAPS 29 /* return currently set traps */ +#define REQ_ADD_TRAP 30 /* add a trap */ +#define REQ_CLR_TRAP 31 /* clear a trap */ +#define REQ_REQUEST_KEY 32 /* define a new request keyid */ +#define REQ_CONTROL_KEY 33 /* define a new control keyid */ +#define REQ_GET_CTLSTATS 34 /* get stats from the control module */ +#define REQ_GET_LEAPINFO 35 /* (not used) */ +#define REQ_GET_CLOCKINFO 36 /* get clock information */ +#define REQ_SET_CLKFUDGE 37 /* set clock fudge factors */ +#define REQ_GET_KERNEL 38 /* get kernel pll/pps information */ +#define REQ_GET_CLKBUGINFO 39 /* get clock debugging info */ +#define REQ_SET_PRECISION 41 /* (not used) */ +#define REQ_MON_GETLIST_1 42 /* return collected v1 monitor data */ +#define REQ_HOSTNAME_ASSOCID 43 /* Here is a hostname + assoc_id */ +#define REQ_IF_STATS 44 /* get interface statistics */ +#define REQ_IF_RELOAD 45 /* reload interface list */ + +/* Determine size of pre-v6 version of structures */ +#define v4sizeof(type) offsetof(type, v6_flag) + +/* + * Flags in the peer information returns + */ +#define INFO_FLAG_CONFIG 0x1 +#define INFO_FLAG_SYSPEER 0x2 +#define INFO_FLAG_BURST 0x4 +#define INFO_FLAG_REFCLOCK 0x8 +#define INFO_FLAG_PREFER 0x10 +#define INFO_FLAG_AUTHENABLE 0x20 +#define INFO_FLAG_SEL_CANDIDATE 0x40 +#define INFO_FLAG_SHORTLIST 0x80 +#define INFO_FLAG_IBURST 0x100 + +/* + * Flags in the system information returns + */ +#define INFO_FLAG_BCLIENT 0x1 +#define INFO_FLAG_AUTHENTICATE 0x2 +#define INFO_FLAG_NTP 0x4 +#define INFO_FLAG_KERNEL 0x8 +#define INFO_FLAG_MONITOR 0x40 +#define INFO_FLAG_FILEGEN 0x80 +#define INFO_FLAG_CAL 0x10 +#define INFO_FLAG_PPS_SYNC 0x20 + + + +/* + * System info. Mostly the sys.* variables, plus a few unique to + * the implementation. + */ +struct info_sys { + uint32_t peer; /* system peer address (v4) */ + u_char peer_mode; /* mode we are syncing to peer in */ + u_char leap; /* system leap bits */ + u_char stratum; /* our stratum */ + s_char precision; /* local clock precision */ + s_fp rootdelay; /* delay from sync source */ + u_fp rootdispersion; /* dispersion from sync source */ + uint32_t refid; /* reference ID of sync source */ + l_fp reftime; /* system reference time */ + uint32_t poll; /* system poll interval */ + u_char flags; /* system flags */ + u_char unused1; /* unused */ + u_char unused2; /* unused */ + u_char unused3; /* unused */ + s_fp bdelay; /* default broadcast offset */ + s_fp frequency; /* frequency residual (scaled ppm) */ + l_fp authdelay; /* default authentication delay */ + u_fp stability; /* clock stability (scaled ppm) */ + u_int v6_flag; /* is this v6 or not */ + u_int unused4; /* unused, padding for peer6 */ + struct in6_addr peer6; /* system peer address (v6) */ +}; + +/* + * Structure for carrying system flags. + */ +struct conf_sys_flags { + uint32_t flags; +}; + +/* + * System flags we can set/clear + */ +#define SYS_FLAG_BCLIENT 0x01 +#define SYS_FLAG_PPS 0x02 +#define SYS_FLAG_NTP 0x04 +#define SYS_FLAG_KERNEL 0x08 +#define SYS_FLAG_MONITOR 0x10 +#define SYS_FLAG_FILEGEN 0x20 +#define SYS_FLAG_AUTH 0x40 +#define SYS_FLAG_CAL 0x80 + +#define NTP_VERSION ((u_char)4) + +#define DEFTIMEOUT (5) /* 5 second time out */ +#define DEFSTIMEOUT (2) /* 2 second time out after first */ + +#define INITDATASIZE (sizeof(struct resp_pkt) * 16) +#define INCDATASIZE (sizeof(struct resp_pkt) * 8) + +/* how many time select() will retry on EINTR */ +#define NTP_EINTR_RETRIES 5 + +#endif /* NTPDCONTROL_H */ diff --git a/src/ptpd/src/dep/outlierfilter.c b/src/ptpd/src/dep/outlierfilter.c new file mode 100644 index 0000000..fe3efe1 --- /dev/null +++ b/src/ptpd/src/dep/outlierfilter.c @@ -0,0 +1,369 @@ +/*- + * Copyright (c) 2014 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file outlierfilter.c + * @date Fri Aug 22 16:18:33 2014 + * + * @brief Code to handle outlier filter routines + * + * Outlier filter mechanics extracted into separate source + * in preparation for 2.4's OOP-like model + */ + +#include "../ptpd.h" + +#ifdef LOCAL_PREFIX +#undef LOCAL_PREFIX +#endif + +#define LOCAL_PREFIX "OutlierFilter" + +static int outlierFilterInit(OutlierFilter *filter, OutlierFilterConfig *config, const char* id); +static int outlierFilterReset(OutlierFilter *filter); +static int outlierFilterShutdown(OutlierFilter *filter); +static int outlierFilterTune(OutlierFilter *filter); +static void outlierFilterUpdate(OutlierFilter *filter); +static Boolean outlierFilterConfigure(OutlierFilter *filter, OutlierFilterConfig *config); +static Boolean outlierFilterFilter(OutlierFilter *filter, double sample); +static int outlierFilterDisplay(OutlierFilter *filter); + +int +outlierFilterSetup(OutlierFilter *filter) +{ + + if(filter == NULL) { + return 0; + } + + memset(filter, 0, sizeof(OutlierFilter)); + + filter->init = outlierFilterInit; + filter->shutdown = outlierFilterShutdown; + filter->reset = outlierFilterReset; + filter->filter = outlierFilterFilter; + filter->display = outlierFilterDisplay; + filter->configure = outlierFilterConfigure; + filter->update = outlierFilterUpdate; + + return 1; + +} + +static int +outlierFilterInit(OutlierFilter *filter, OutlierFilterConfig *config, const char* id) { + + filter->config = *config; + + if (config->enabled) { + filter->rawStats = createDoubleMovingStdDev(config->capacity); + strncpy(filter->id, id, OUTLIERFILTER_MAX_DESC); + strncpy(filter->rawStats->identifier, id, 10); + filter->filteredStats = createDoubleMovingMean(config->capacity); + filter->threshold = config->threshold; + } else { + filter->rawStats = NULL; + filter->filteredStats = NULL; + } + resetDoublePermanentMean(&filter->outlierStats); + resetDoublePermanentMean(&filter->acceptedStats); + + filter->delayCredit = filter->config.delayCredit; + + return 1; + +} + +static int +outlierFilterReset(OutlierFilter *filter) { + + resetDoubleMovingStdDev(filter->rawStats); + resetDoubleMovingMean(filter->filteredStats); + filter->lastOutlier = FALSE; + filter->threshold = filter->config.threshold; + resetDoublePermanentMean(&filter->outlierStats); + resetDoublePermanentMean(&filter->acceptedStats); + filter->delay = 0; + filter->totalDelay = 0; + filter->delayCredit = filter->config.delayCredit; + filter->blocking = FALSE; + + return 1; + +} + +static int +outlierFilterShutdown(OutlierFilter *filter) { + + if(filter->rawStats != NULL ) { + freeDoubleMovingStdDev(&filter->rawStats); + } + + if(filter->filteredStats != NULL ) { + freeDoubleMovingMean(&filter->filteredStats); + } + resetDoublePermanentMean(&filter->outlierStats); + resetDoublePermanentMean(&filter->acceptedStats); + + return 1; + +} + +static +int outlierFilterTune(OutlierFilter *filter) { + + DBG("tuning outlier filter: %s\n", filter->id); + + if(!filter->config.autoTune || (filter->autoTuneSamples < 1)) { + return 1; + } + + filter->autoTuneScore = round(((filter->autoTuneOutliers + 0.0) / (filter->autoTuneSamples + 0.0)) * 100.0); + + if(filter->autoTuneScore < filter->config.minPercent) { + + filter->threshold -= filter->config.thresholdStep; + + if(filter->threshold < filter->config.minThreshold) { + filter->threshold = filter->config.minThreshold; + } + + } + + if(filter->autoTuneScore > filter->config.maxPercent) { + + filter->threshold += filter->config.thresholdStep; + + if(filter->threshold > filter->config.maxThreshold) { + filter->threshold = filter->config.maxThreshold; + } + + } + + DBG("%s filter autotune: counter %d, samples %d, outliers, %d percentage %d, new threshold %.02f\n", + filter->id, filter->rawStats->meanContainer->counter, filter->autoTuneSamples, + filter->autoTuneOutliers, filter->autoTuneScore, + filter->threshold); + + filter->autoTuneSamples = 0; + filter->autoTuneOutliers = 0; + + return 1; + +} + +static void +outlierFilterUpdate(OutlierFilter *filter) +{ + + /* not used yet */ + + +} + +static Boolean +outlierFilterConfigure(OutlierFilter *filter, OutlierFilterConfig *config) +{ + + /* restart needed */ + if(filter->config.capacity != config->capacity) { + return TRUE; + } + + filter->config = *config; + + filter->reset(filter); + + return FALSE; + +} + +/* 2 x fairy dust, 3 x unicorn droppings, 1 x magic beanstalk juice. blend, spray on the affected area twice per day */ +static Boolean +outlierFilterFilter(OutlierFilter *filter, double sample) +{ + + /* true = accepted - this is to tell the user if we advised to throw away the sample */ + Boolean ret = TRUE; + + /* step change: outlier mean - accepted mean from last sampling period */ + double step = 0.0; + + if(!filter->config.enabled) { + filter->output = sample; + return TRUE; + } + + step = fabs(filter->outlierStats.mean - filter->acceptedStats.bufferedMean); + + if(filter->config.autoTune) { + filter->autoTuneSamples++; + } + + /* no outlier first - more convenient this way */ + if(!isDoublePeircesOutlier(filter->rawStats, sample, filter->threshold) && (filter->delay == 0)) { + + filter->lastOutlier = FALSE; + filter->output = sample; + + /* filter is about to accept after a blocking period */ + if(filter->consecutiveOutliers) { + DBG_LOCAL_ID(filter,"consecutive: %d, mean: %.09fm accepted bmean: %.09f\n", filter->consecutiveOutliers, + filter->outlierStats.mean,filter->acceptedStats.bufferedMean); + + /* we are about to open up but the offset has risen above step level, we will block again, but not forever */ + if(filter->config.stepDelay && + (fabs(filter->acceptedStats.bufferedMean) < ((filter->config.stepThreshold + 0.0) / 1E9)) && + (step > ((filter->config.stepLevel + 0.0) / 1E9))) { + /* if we're to enter blocking, we need 2 * consecutiveOutliers credit */ + /* if we're already blocking, we just need enough credit */ + /* if we're already blocking, make sure we block no more than maxDelay */ + if((filter->blocking && ((filter->config.maxDelay > filter->totalDelay) && (filter->delayCredit >= filter->consecutiveOutliers))) || + (!filter->blocking && (filter->delayCredit >= filter->consecutiveOutliers * 2 ))) { + if(!filter->blocking) { + INFO_LOCAL_ID(filter,"%.03f us step detected, filter will now block\n", step * 1E6); + } + DBG_LOCAL_ID(filter,"step: %.09f, credit left %d, requesting %d\n",step, + filter->delayCredit, filter->consecutiveOutliers); + filter->delay = filter->consecutiveOutliers; + filter->totalDelay += filter->consecutiveOutliers; + filter->delayCredit -= filter->consecutiveOutliers; + filter->blocking = TRUE; + resetDoublePermanentMean(&filter->outlierStats); + filter->lastOutlier = TRUE; + DBG_LOCAL_ID(filter,"maxdelay: %d, totaldelay: %d\n",filter->config.maxDelay, filter->totalDelay); + return FALSE; + + + /* much love for the ultra magnetic, cause everybody knows you never got enough credit */ + /* we either ran out of credit while blocking, or we did not have enough to start with */ + } else { + if(filter->blocking) { + INFO_LOCAL_ID(filter,"blocking time exhausted, filter will stop blocking\n"); + } else { + INFO_LOCAL_ID(filter,"%.03f us step detected but filter cannot block\n", step * 1E6); + } + DBG_LOCAL_ID(filter,"credit out (has %d, needed %d)\n", filter->delayCredit, filter->consecutiveOutliers); + } + /* NO STEP */ + } else { + + if (filter->blocking) { + INFO_LOCAL_ID(filter,"step event over, filter will stop blocking\n"); + } + filter->blocking = FALSE; + } + + if(filter->totalDelay != 0) { + DBG_LOCAL_ID(filter,"Total waited %d\n", filter->totalDelay); + filter->totalDelay = 0; + } + } + + filter->consecutiveOutliers = 0; + resetDoublePermanentMean(&filter->outlierStats); + feedDoublePermanentMean(&filter->acceptedStats, sample); + + /* it's an outlier, Sir! */ + } else { + + filter->lastOutlier = TRUE; + feedDoublePermanentMean(&filter->outlierStats, sample); + + if(filter->delay) { + DBG_LOCAL_ID(filter,"delay left: %d\n", filter->delay); + filter->delay--; + return FALSE; + } + + filter->autoTuneOutliers++; + filter->consecutiveOutliers++; + + if(filter->config.discard) { + ret = FALSE; + } else { + filter->output = filter->filteredStats->mean; + } + + + DBG_LOCAL_ID(filter,"Outlier: %.09f\n", sample); + /* Allow [weight] * [deviation from mean] to influence std dev in the next outlier checks */ + sample = filter->rawStats->meanContainer->mean + filter->config.weight * ( sample - filter->rawStats->meanContainer->mean); + + } + + /* keep stats containers updated */ + feedDoubleMovingStdDev(filter->rawStats, sample); + feedDoubleMovingMean(filter->filteredStats, filter->output); + + /* re-tune filter twice per window */ + if( (filter->rawStats->meanContainer->counter % ( filter->rawStats->meanContainer->capacity / 2)) == 0) { + outlierFilterTune(filter); + } + + /* replenish filter credit once per window */ + if( filter->config.stepDelay && ((filter->rawStats->meanContainer->counter % filter->rawStats->meanContainer->capacity) == 0)) { + filter->delayCredit += filter->config.creditIncrement; + if(filter->delayCredit >= filter->config.delayCredit) { + filter->delayCredit = filter->config.delayCredit; + } + DBG_LOCAL_ID(filter,"credit added, now %d\n", filter->delayCredit); + } + + return ret; + +} + + +static int outlierFilterDisplay(OutlierFilter *filter) { + + char *id = filter->id; + + INFO("%s outlier filter info:\n", + id); + INFO(" %s.threshold : %.02f\n", + id, filter->config.threshold); + INFO(" %s.autotune : %s\n", + id, (filter->config.autoTune) ? "Y" : "N"); + + if(!filter->config.autoTune) { + return 0; + } + + INFO(" %s.percent : %d\n", + id, filter->autoTuneScore); + INFO(" %s.minThreshold : %.02f\n", + id, filter->config.minThreshold); + INFO(" %s.maxThreshold : %.02f\n", + id, filter->config.maxThreshold); + INFO(" %s.thresholdStep : %.02f\n", + id, filter->config.thresholdStep); + + + return 1; +} + diff --git a/src/ptpd/src/dep/outlierfilter.h b/src/ptpd/src/dep/outlierfilter.h new file mode 100644 index 0000000..873a8ff --- /dev/null +++ b/src/ptpd/src/dep/outlierfilter.h @@ -0,0 +1,125 @@ +#ifndef OUTLIERFILTER_H_ +#define OUTLIERFILTER_H_ + +#include + +#define OUTLIERFILTER_MAX_DESC 20 + +/*- + * Copyright (c) 2014 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file outlierfilter.h + * @date Fri Aug 22 16:18:33 2014 + * + * @brief Function definitions for the outlier filter + * + */ + +typedef struct { + + Boolean enabled; + Boolean discard; + Boolean autoTune; + Boolean stepDelay; + + /* the user may have some conditions under which we should not filter. + this is to hint them that we would like to always filter. + */ + Boolean alwaysFilter; + + int capacity; + double threshold; + double weight; + + int minPercent; + int maxPercent; + double thresholdStep; + + double minThreshold; + double maxThreshold; + + /* if absolute value outside this threshold, do not filter. negative = disabled */ + double maxAcceptable; + + /* accepted sample threshold at which step detection is active */ + int32_t stepThreshold; + /* value which is considered a step */ + int32_t stepLevel; + + /* delay credit - gets used for waiting when step detected */ + int delayCredit; + + /* credit replenishment unit */ + int creditIncrement; + + /* safeguard - maximum credit */ + int maxDelay; + +} OutlierFilterConfig; + +typedef struct OutlierFilter OutlierFilter; + +struct OutlierFilter { + + char id [OUTLIERFILTER_MAX_DESC + 1]; + OutlierFilterConfig config; + + DoubleMovingStdDev* rawStats; + DoubleMovingMean* filteredStats; + DoublePermanentMean outlierStats; + DoublePermanentMean acceptedStats; + Boolean lastOutlier; + double threshold; + double output; + int autoTuneSamples; + int autoTuneOutliers; + int autoTuneScore; + int consecutiveOutliers; + int delay; + int totalDelay; + int delayCredit; + Boolean blocking; + + /* 'methods' */ + + int (*init) (OutlierFilter *filter, OutlierFilterConfig *config, const char *id); + int (*shutdown) (OutlierFilter *filter); + int (*reset) (OutlierFilter *filter); + Boolean (*filter) (OutlierFilter *filter, double sample); + Boolean (*configure) (OutlierFilter *filter, OutlierFilterConfig *config); + int (*display) (OutlierFilter *filter); + void (*update) (OutlierFilter *filter); + +}; + + + +/* OutlierFilter *oFilterCreate(OutlierFilterConfig *options, const char *id); */ +int outlierFilterSetup(OutlierFilter *filter); + +#endif /*OUTLIERFILTER_H_*/ diff --git a/src/ptpd/src/dep/ptpd_dep.h b/src/ptpd/src/dep/ptpd_dep.h new file mode 100644 index 0000000..2ae313b --- /dev/null +++ b/src/ptpd/src/dep/ptpd_dep.h @@ -0,0 +1,518 @@ +/** + * @file ptpd_dep.h + * + * @brief External definitions for inclusion elsewhere. + * + * + */ + +#ifndef PTPD_DEP_H_ +#define PTPD_DEP_H_ + +#ifdef RUNTIME_DEBUG +#undef PTPD_DBGV +#define PTPD_DBGV +#endif + + /** \name System messages*/ + /**\{*/ + + +// Syslog ordering. We define extra debug levels above LOG_DEBUG for internal use - but message() doesn't pass these to SysLog + +// extended from +#define LOG_DEBUG1 7 +#define LOG_DEBUG2 8 +#define LOG_DEBUG3 9 +#define LOG_DEBUGV 9 + + +#define EMERGENCY(x, ...) logMessage(LOG_EMERG, x, ##__VA_ARGS__) +#define ALERT(x, ...) logMessage(LOG_ALERT, x, ##__VA_ARGS__) +#define CRITICAL(x, ...) logMessage(LOG_CRIT, x, ##__VA_ARGS__) +#define ERROR(x, ...) logMessage(LOG_ERR, x, ##__VA_ARGS__) +#define PERROR(x, ...) logMessage(LOG_ERR, x " (strerror: %s)\n", ##__VA_ARGS__, strerror(errno)) +#define WARNING(x, ...) logMessage(LOG_WARNING, x, ##__VA_ARGS__) +#define NOTIFY(x, ...) logMessage(LOG_NOTICE, x, ##__VA_ARGS__) +#define NOTICE(x, ...) logMessage(LOG_NOTICE, x, ##__VA_ARGS__) +#define INFO(x, ...) logMessage(LOG_INFO, x, ##__VA_ARGS__) + +#define EMERGENCY_LOCAL(x, ...) EMERGENCY(LOCAL_PREFIX": " x,##__VA_ARGS__) +#define ALERT_LOCAL(x, ...) ALERT(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define CRITICAL_LOCAL(x, ...) CRITICAL(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define ERROR_LOCAL(x, ...) ERROR(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define PERROR_LOCAL(x, ...) PERROR(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define WARNING_LOCAL(x, ...) WARNING(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define NOTIFY_LOCAL(x, ...) NOTIFY(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define NOTICE_LOCAL(x, ...) NOTICE(LOCAL_PREFIX": " x, ##__VA_ARGS__) +#define INFO_LOCAL(x, ...) INFO(LOCAL_PREFIX": " x, ##__VA_ARGS__) + +#define EMERGENCY_LOCAL_ID(o,x, ...) EMERGENCY(LOCAL_PREFIX".%s: "x,o->id,##__VA_ARGS__) +#define ALERT_LOCAL_ID(o,x, ...) ALERT(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define CRITICAL_LOCAL_ID(o,x, ...) CRITICAL(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define ERROR_LOCAL_ID(o,x, ...) ERROR(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define PERROR_LOCAL_ID(o,x, ...) PERROR(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define WARNING_LOCAL_ID(o,x, ...) WARNING(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define NOTIFY_LOCAL_ID(o,x, ...) NOTIFY(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define NOTICE_LOCAL_ID(o,x, ...) NOTICE(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) +#define INFO_LOCAL_ID(o,x, ...) INFO(LOCAL_PREFIX".%s: "x,o->id, ##__VA_ARGS__) + +#if defined(__FILE__) && defined(__LINE__) +#define MARKER INFO("Marker: %s:%d\n", __FILE__, __LINE__) +#else +#define MARKER INFO("Marker\n") +#endif + +#include + + +/* + list of per-module defines: + +./dep/sys.c:#define PRINT_MAC_ADDRESSES +./dep/timer.c:#define US_TIMER_INTERVAL 125000 +*/ +#define USE_BINDTODEVICE + + + +// enable this line to show debug numbers in nanoseconds instead of microseconds +// #define DEBUG_IN_NS + +#define DBG_UNIT_US (1000) +#define DBG_UNIT_NS (1) + +#ifdef DEBUG_IN_NS +#define DBG_UNIT DBG_UNIT_NS +#else +#define DBG_UNIT DBG_UNIT_US +#endif + + + + +/** \}*/ + +/** \name Debug messages*/ + /**\{*/ + +#ifdef PTPD_DBGV +#undef PTPD_DBG +#undef PTPD_DBG2 +#define PTPD_DBG +#define PTPD_DBG2 + +#define DBGV(x, ...) logMessage(LOG_DEBUGV, x, ##__VA_ARGS__) +#define DBGV_LOCAL(x, ...) DBGV(LOCAL_PREFIX": " x,##__VA_ARGS__) +#define DBGV_LOCAL_ID(o,x, ...) DBGV(LOCAL_PREFIX".%s:"x,o->id,##__VA_ARGS__) +#else +#define DBGV(x, ...) +#define DBGV_LOCAL(x, ...) +#define DBGV_LOCAL_ID(x, ...) +#endif + +/* + * new debug level DBG2: + * this is above DBG(), but below DBGV() (to avoid changing hundreds of lines) + */ + + +#ifdef PTPD_DBG2 +#undef PTPD_DBG +#define PTPD_DBG +#define DBG2(x, ...) logMessage(LOG_DEBUG2, x, ##__VA_ARGS__) +#define DBG2_LOCAL(x, ...) DBG2(LOCAL_PREFIX": " x,##__VA_ARGS__) +#define DBG2_LOCAL_ID(o,x, ...) DBG2(LOCAL_PREFIX".%s:"x,o->id,##__VA_ARGS__) + +#else + +#define DBG2(x, ...) +#define DBG2_LOCAL(x, ...) +#define DBG2_LOCAL_ID(x, ...) +#endif + +#ifdef PTPD_DBG +#define DBG(x, ...) logMessage(LOG_DEBUG, x, ##__VA_ARGS__) +#define DBG_LOCAL(x, ...) DBG(LOCAL_PREFIX": " x,##__VA_ARGS__) +#define DBG_LOCAL_ID(o,x, ...) DBG(LOCAL_PREFIX".%s:"x,o->id,##__VA_ARGS__) +#else +#define DBG(x, ...) +#define DBG_LOCAL(x, ...) +#define DBG_LOCAL_ID(x, ...) +#endif + +/** \}*/ + +/** \name Endian corrections*/ + /**\{*/ + +#if defined(PTPD_MSBF) +#define shift8(x,y) ( (x) << ((3-y)<<3) ) +#define shift16(x,y) ( (x) << ((1-y)<<4) ) +#elif defined(PTPD_LSBF) +#define shift8(x,y) ( (x) << ((y)<<3) ) +#define shift16(x,y) ( (x) << ((y)<<4) ) +#endif + +#define flip16(x) htons(x) +#define flip32(x) htonl(x) + +/* i don't know any target platforms that do not have htons and htonl, + but here are generic funtions just in case */ +/* +#if defined(PTPD_MSBF) +#define flip16(x) (x) +#define flip32(x) (x) +#elif defined(PTPD_LSBF) +static inline Integer16 flip16(Integer16 x) +{ + return (((x) >> 8) & 0x00ff) | (((x) << 8) & 0xff00); +} + +static inline Integer32 flip32(x) +{ + return (((x) >> 24) & 0x000000ff) | (((x) >> 8 ) & 0x0000ff00) | + (((x) << 8 ) & 0x00ff0000) | (((x) << 24) & 0xff000000); +} +#endif +*/ + +/** \}*/ + + +/** \name Bit array manipulations*/ + /**\{*/ + +#define getFlag(x,y) !!( *(UInteger8*)((x)+((y)<8?1:0)) & (1<<((y)<8?(y):(y)-8)) ) +#define setFlag(x,y) ( *(UInteger8*)((x)+((y)<8?1:0)) |= 1<<((y)<8?(y):(y)-8) ) +#define clearFlag(x,y) ( *(UInteger8*)((x)+((y)<8?1:0)) &= ~(1<<((y)<8?(y):(y)-8)) ) +/** \}*/ + +#define DEFAULT_TOKEN_DELIM ", ;\t" + +/* + * foreach loop across substrings from var, delimited by delim, placing + * each token in targetvar on iteration, using id variable name prefix + * to allow nesting (each loop uses an individual set of variables) + */ +#define foreach_token_begin(id, var, targetvar, delim) {\ + char* id_stash; \ + char* id_text_; \ + char* id_text__; \ + char* targetvar; \ + id_text_=strdup(var); \ + for(id_text__ = id_text_;; id_text__=NULL) { \ + targetvar = strtok_r(id_text__, delim, &id_stash); \ + if(targetvar==NULL) break; + +#define foreach_token_end(id) } \ + if(id_text_ != NULL) { \ + free(id_text_); \ + }\ +} + +/** \name msg.c + *-Pack and unpack PTP messages */ + /**\{*/ + +void msgUnpackHeader(Octet * buf,MsgHeader*); +void msgUnpackAnnounce (Octet * buf,MsgAnnounce*); +void msgUnpackSync(Octet * buf,MsgSync*); +void msgUnpackFollowUp(Octet * buf,MsgFollowUp*); +void msgUnpackDelayReq(Octet * buf, MsgDelayReq * delayreq); +void msgUnpackDelayResp(Octet * buf,MsgDelayResp *); +void msgUnpackPdelayReq(Octet * buf,MsgPdelayReq*); +void msgUnpackPdelayResp(Octet * buf,MsgPdelayResp*); +void msgUnpackPdelayRespFollowUp(Octet * buf,MsgPdelayRespFollowUp*); +Boolean msgUnpackManagement(Octet * buf,MsgManagement*, MsgHeader*, PtpClock *ptpClock, const int tlvOffset); +Boolean msgUnpackSignaling(Octet * buf,MsgSignaling*, MsgHeader*, PtpClock *ptpClock, const int tlvOffset); +// ##### SCORE MODIFICATION BEGIN ##### +void msgPackHeader(Octet * buf,PtpClock*, MsgHeader*); +// ##### SCORE MODIFICATION END ##### +#ifndef PTPD_SLAVE_ONLY +void msgPackAnnounce(Octet * buf, UInteger16, Timestamp*, PtpClock*); +void msgPackSync(Octet * buf, UInteger16, Timestamp*, PtpClock*); +#endif /* PTPD_SLAVE_ONLY */ +void msgPackFollowUp(Octet * buf,Timestamp*,PtpClock*, const UInteger16); +void msgPackDelayReq(Octet * buf,Timestamp *,PtpClock *); +void msgPackDelayResp(Octet * buf,MsgHeader *,Timestamp *,PtpClock *); +void msgPackPdelayReq(Octet * buf,Timestamp*,PtpClock*); +void msgPackPdelayResp(Octet * buf,MsgHeader*,Timestamp*,PtpClock*); +void msgPackPdelayRespFollowUp(Octet * buf,MsgHeader*,Timestamp*,PtpClock*, const UInteger16); +void msgPackManagement(Octet * buf,MsgManagement*,PtpClock*); +void msgPackSignaling(Octet * buf,MsgSignaling*,PtpClock*); +void msgPackManagementRespAck(Octet *,MsgManagement*,PtpClock*); +void msgPackManagementTLV(Octet *,MsgManagement*, PtpClock*); +void msgPackSignalingTLV(Octet *,MsgSignaling*, PtpClock*); +void msgPackManagementErrorStatusTLV(Octet *,MsgManagement*,PtpClock*); + +void freeMMErrorStatusTLV(ManagementTLV*); +void freeMMTLV(ManagementTLV*); + +void msgDump(PtpClock *ptpClock); +void msgDebugHeader(MsgHeader *header); +void msgDebugSync(MsgSync *sync); +void msgDebugAnnounce(MsgAnnounce *announce); +void msgDebugDelayReq(MsgDelayReq *req); +void msgDebugFollowUp(MsgFollowUp *follow); +void msgDebugDelayResp(MsgDelayResp *resp); +void msgDebugManagement(MsgManagement *manage); + +void copyClockIdentity( ClockIdentity dest, ClockIdentity src); +void copyPortIdentity( PortIdentity * dest, PortIdentity * src); + +void unpackMsgSignaling(Octet *, MsgSignaling*, PtpClock*); +void packMsgSignaling(MsgSignaling*, Octet *); +void unpackSignalingTLV(Octet*, MsgSignaling*, PtpClock*); +void packSignalingTLV(SignalingTLV*, Octet*); +void freeSignalingTLV(MsgSignaling*); + +void unpackMsgManagement(Octet *, MsgManagement*, PtpClock*); +void packMsgManagement(MsgManagement*, Octet *); +void unpackManagementTLV(Octet*, int, MsgManagement*, PtpClock*); +void packManagementTLV(ManagementTLV*, Octet*); +void freeManagementTLV(MsgManagement*); + +int unpackMMClockDescription( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMClockDescription( MsgManagement*, Octet*); +void freeMMClockDescription( MMClockDescription*); +int unpackMMUserDescription( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMUserDescription( MsgManagement*, Octet*); +void freeMMUserDescription( MMUserDescription*); +int unpackMMErrorStatus( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMErrorStatus( MsgManagement*, Octet*); +void freeMMErrorStatus( MMErrorStatus*); +int unpackMMInitialize( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMInitialize( MsgManagement*, Octet*); +int unpackMMDefaultDataSet( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMDefaultDataSet( MsgManagement*, Octet*); +int unpackMMCurrentDataSet( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMCurrentDataSet( MsgManagement*, Octet*); +int unpackMMParentDataSet( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMParentDataSet( MsgManagement*, Octet*); +int unpackMMTimePropertiesDataSet( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMTimePropertiesDataSet( MsgManagement*, Octet*); +int unpackMMPortDataSet( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMPortDataSet( MsgManagement*, Octet*); +int unpackMMPriority1( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMPriority1( MsgManagement*, Octet*); +int unpackMMPriority2( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMPriority2( MsgManagement*, Octet*); +int unpackMMDomain( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMDomain( MsgManagement*, Octet*); +int unpackMMSlaveOnly( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMSlaveOnly( MsgManagement*, Octet* ); +int unpackMMLogAnnounceInterval( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMLogAnnounceInterval( MsgManagement*, Octet*); +int unpackMMAnnounceReceiptTimeout( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMAnnounceReceiptTimeout( MsgManagement*, Octet*); +int unpackMMLogSyncInterval( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMLogSyncInterval( MsgManagement*, Octet*); +int unpackMMVersionNumber( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMVersionNumber( MsgManagement*, Octet*); +int unpackMMTime( Octet* buf, int, MsgManagement*, PtpClock * ); +UInteger16 packMMTime( MsgManagement*, Octet*); +int unpackMMClockAccuracy( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMClockAccuracy( MsgManagement*, Octet*); +int unpackMMUtcProperties( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMUtcProperties( MsgManagement*, Octet*); +int unpackMMTraceabilityProperties( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMTraceabilityProperties( MsgManagement*, Octet*); +int unpackMMTimescaleProperties( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMTimescaleProperties( MsgManagement*, Octet*); +int unpackMMUnicastNegotiationEnable( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMUnicastNegotiationEnable( MsgManagement*, Octet*); +int unpackMMDelayMechanism( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMDelayMechanism( MsgManagement*, Octet*); +int unpackMMLogMinPdelayReqInterval( Octet* buf, int, MsgManagement*, PtpClock* ); +UInteger16 packMMLogMinPdelayReqInterval( MsgManagement*, Octet*); + +/* Signaling TLV packing / unpacking functions */ +void unpackSMRequestUnicastTransmission( Octet* buf, MsgSignaling*, PtpClock* ); +UInteger16 packSMRequestUnicastTransmission( MsgSignaling*, Octet*); +void unpackSMGrantUnicastTransmission( Octet* buf, MsgSignaling*, PtpClock* ); +UInteger16 packSMGrantUnicastTransmission( MsgSignaling*, Octet*); +void unpackSMCancelUnicastTransmission( Octet* buf, MsgSignaling*, PtpClock* ); +UInteger16 packSMCancelUnicastTransmission( MsgSignaling*, Octet*); +void unpackSMAcknowledgeCancelUnicastTransmission( Octet* buf, MsgSignaling*, PtpClock* ); +UInteger16 packSMAcknowledgeCancelUnicastTransmission( MsgSignaling*, Octet*); + +void unpackPortAddress( Octet* buf, PortAddress*, PtpClock*); +void packPortAddress( PortAddress*, Octet*); +void freePortAddress( PortAddress*); +void unpackPTPText( Octet* buf, PTPText*, PtpClock*); +void packPTPText( PTPText*, Octet*); +void freePTPText( PTPText*); +void unpackPhysicalAddress( Octet* buf, PhysicalAddress*, PtpClock*); +void packPhysicalAddress( PhysicalAddress*, Octet*); +void freePhysicalAddress( PhysicalAddress*); +void unpackClockIdentity( Octet* buf, ClockIdentity *c, PtpClock*); +void packClockIdentity( ClockIdentity *c, Octet* buf); +void freeClockIdentity( ClockIdentity *c); +void unpackClockQuality( Octet* buf, ClockQuality *c, PtpClock*); +void packClockQuality( ClockQuality *c, Octet* buf); +void freeClockQuality( ClockQuality *c); +void unpackTimeInterval( Octet* buf, TimeInterval *t, PtpClock*); +void packTimeInterval( TimeInterval *t, Octet* buf); +void freeTimeInterval( TimeInterval *t); +void unpackPortIdentity( Octet* buf, PortIdentity *p, PtpClock*); +void packPortIdentity( PortIdentity *p, Octet* buf); +void freePortIdentity( PortIdentity *p); +void unpackTimestamp( Octet* buf, Timestamp *t, PtpClock*); +void packTimestamp( Timestamp *t, Octet* buf); +void freeTimestamp( Timestamp *t); +UInteger16 msgPackManagementResponse(Octet * buf,MsgHeader*,MsgManagement*,PtpClock*); +/** \}*/ + +/** \name net.c (Unix API dependent) + * -Init network stuff, send and receive datas*/ + /**\{*/ + +Boolean testInterface(char* ifaceName, const RunTimeOpts* rtOpts); +Boolean netInit(NetPath*,RunTimeOpts*,PtpClock*); +Boolean netShutdown(NetPath*); +int netSelect(TimeInternal*,NetPath*,fd_set*); +ssize_t netRecvEvent(Octet*,TimeInternal*,NetPath*,int); +ssize_t netRecvGeneral(Octet*,NetPath*); +ssize_t netSendEvent(Octet*,UInteger16,NetPath*,const RunTimeOpts*,Integer32,TimeInternal*); +ssize_t netSendGeneral(Octet*,UInteger16,NetPath*,const RunTimeOpts*,Integer32 ); +ssize_t netSendPeerGeneral(Octet*,UInteger16,NetPath*,const RunTimeOpts*, Integer32); +ssize_t netSendPeerEvent(Octet*,UInteger16,NetPath*,const RunTimeOpts*,Integer32,TimeInternal*); +Boolean netRefreshIGMP(NetPath *, const RunTimeOpts *, PtpClock *); +Boolean hostLookup(const char* hostname, Integer32* addr); + +/** \}*/ + +#if defined PTPD_SNMP +/** \name snmp.c (SNMP subsystem) + * -Handle SNMP subsystem*/ + /**\{*/ + +void snmpInit(RunTimeOpts *, PtpClock *); +void snmpShutdown(); +void eventHandler_snmp(AlarmEntry *alarm); +void alarmHandler_snmp(AlarmEntry *alarm); + +//void sendNotif(int eventType, PtpEventData *eventData); + + +/** \}*/ +#endif + +/** \name servo.c + * -Clock servo*/ + /**\{*/ + +void initClock(const RunTimeOpts*,PtpClock*); +void updatePeerDelay (one_way_delay_filter*, const RunTimeOpts*,PtpClock*,TimeInternal*,Boolean); +void updateDelay (one_way_delay_filter*, const RunTimeOpts*, PtpClock*,TimeInternal*); +void updateOffset(TimeInternal*,TimeInternal*, + offset_from_master_filter*,const RunTimeOpts*,PtpClock*,TimeInternal*); +void checkOffset(const RunTimeOpts*, PtpClock*); +void updateClock(const RunTimeOpts*,PtpClock*); +void stepClock(const RunTimeOpts * rtOpts, PtpClock * ptpClock); + +/** \}*/ + +/** \name startup.c (Unix API dependent) + * -Handle with runtime options*/ + /**\{*/ +int setCpuAffinity(int cpu); +int logToFile(RunTimeOpts * rtOpts); +int recordToFile(RunTimeOpts * rtOpts); +PtpClock * ptpdStartup(int,char**,Integer16*,RunTimeOpts*); + +void ptpdShutdown(PtpClock * ptpClock); +void checkSignals(RunTimeOpts * rtOpts, PtpClock * ptpClock); +void restartSubsystems(RunTimeOpts *rtOpts, PtpClock *ptpClock); +void applyConfig(dictionary *baseConfig, RunTimeOpts *rtOpts, PtpClock *ptpClock); + +void enable_runtime_debug(void ); +void disable_runtime_debug(void ); + +void ntpSetup(RunTimeOpts *rtOpts, PtpClock *ptpClock); + +#define D_ON do { enable_runtime_debug(); } while (0); +#define D_OFF do { disable_runtime_debug( ); } while (0); + + +/** \}*/ + +/** \name sys.c (Unix API dependent) + * -Manage timing system API*/ + /**\{*/ + +/* new debug methods to debug time variables */ +char *time2st(const TimeInternal * p); +void DBG_time(const char *name, const TimeInternal p); + + +void logMessage(int priority, const char *format, ...); +void updateLogSize(LogFileHandler* handler); +Boolean maintainLogSize(LogFileHandler* handler); +int restartLog(LogFileHandler* handler, Boolean quiet); +void restartLogging(RunTimeOpts* rtOpts); +void stopLogging(RunTimeOpts* rtOpts); +void logStatistics(PtpClock *ptpClock); +void periodicUpdate(const RunTimeOpts *rtOpts, PtpClock *ptpClock); +void displayStatus(PtpClock *ptpClock, const char *prefixMessage); +void displayPortIdentity(PortIdentity *port, const char *prefixMessage); +int snprint_PortIdentity(char *s, int max_len, const PortIdentity *id); +Boolean nanoSleep(TimeInternal*); +void getTime(TimeInternal*); +void getTimeMonotonic(TimeInternal*); +void setTime(TimeInternal*); +#ifdef linux +void setRtc(TimeInternal *); +#endif /* linux */ +double getRand(void); +int lockFile(int fd); +int checkLockStatus(int fd, short lockType, int *lockPid); +int checkFileLockable(const char *fileName, int *lockPid); +Boolean checkOtherLocks(RunTimeOpts *rtOpts); + +void recordSync(UInteger16 sequenceId, TimeInternal * time); + +void adjFreq_wrapper(const RunTimeOpts * rtOpts, PtpClock * ptpClock, double adj); + +Boolean adjFreq(double); +double getAdjFreq(void); + +#ifdef HAVE_SYS_TIMEX_H +void informClockSource(PtpClock* ptpClock); + +/* Helper function to manage ntpadjtime / adjtimex flags */ +void setTimexFlags(int flags, Boolean quiet); +void unsetTimexFlags(int flags, Boolean quiet); +int getTimexFlags(void); +Boolean checkTimexFlags(int flags); + +#if defined(MOD_TAI) && NTP_API == 4 +void setKernelUtcOffset(int utc_offset); +Boolean getKernelUtcOffset(int *utc_offset); +#endif /* MOD_TAI */ + +#endif /* HAVE_SYS_TIMEX_H */ + +/* Observed drift save / recovery functions */ +void restoreDrift(PtpClock * ptpClock, const RunTimeOpts * rtOpts, Boolean quiet); +void saveDrift(PtpClock * ptpClock, const RunTimeOpts * rtOpts, Boolean quiet); + +int parseLeapFile(char * path, LeapSecondInfo *info); + +void +resetWarnings(const RunTimeOpts * rtOpts, PtpClock * ptpClock); + +void setupPIservo(PIservo* servo, const RunTimeOpts* rtOpts); +void resetPIservo(PIservo* servo); +double runPIservo(PIservo* servo, const Integer32 input); + +#ifdef PTPD_STATISTICS +void updatePtpEngineStats (PtpClock* ptpClock, const RunTimeOpts* rtOpts); +#endif /* PTPD_STATISTICS */ + +void writeStatusFile(PtpClock *ptpClock, const RunTimeOpts *rtOpts, Boolean quiet); +void updateXtmp (TimeInternal oldTime, TimeInternal newTime); + + +#endif /*PTPD_DEP_H_*/ diff --git a/src/ptpd/src/dep/servo.c b/src/ptpd/src/dep/servo.c new file mode 100644 index 0000000..435a74c --- /dev/null +++ b/src/ptpd/src/dep/servo.c @@ -0,0 +1,1280 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ +/*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file servo.c + * @date Tue Jul 20 16:19:19 2010 + * + * @brief Code which implements the clock servo in software. + * + * + */ + +#include "../ptpd.h" + +#define CLAMP(var,bound) {\ + if(var < -bound) {\ + var = -bound;\ + }\ + if(var > bound) {\ + var = bound;\ + }\ +} + +#ifdef PTPD_STATISTICS +static void checkServoStable(PtpClock *ptpClock, const RunTimeOpts *rtOpts); +#endif + +void +resetWarnings(const RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + ptpClock->warned_operator_slow_slewing = 0; + ptpClock->warned_operator_fast_slewing = 0; + ptpClock->warnedUnicastCapacity = FALSE; + //ptpClock->seen_servo_stable_first_time = FALSE; +} + +void +initClock(const RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + DBG("initClock\n"); + + /* If we've been suppressing ntpdc error messages, show them once again */ + ptpClock->ntpControl.requestFailed = FALSE; + ptpClock->disabled = rtOpts->portDisabled; + +/* do not reset frequency here - restoreDrift will do it if necessary */ +/* 2.3.1: restoreDrift now always compiled - this is no longer needed */ +#if 0 + ptpClock->servo.observedDrift = 0; +#endif + /* clear vars */ + + /* clean more original filter variables */ + clearTime(&ptpClock->currentDS.offsetFromMaster); + clearTime(&ptpClock->currentDS.meanPathDelay); + clearTime(&ptpClock->delaySM); + clearTime(&ptpClock->delayMS); + + ptpClock->ofm_filt.y = 0; + ptpClock->ofm_filt.nsec_prev = 0; + + ptpClock->mpd_filt.s_exp = 0; /* clears one-way delay filter */ + ptpClock->offsetFirstUpdated = FALSE; + + ptpClock->char_last_msg='I'; + + resetWarnings(rtOpts, ptpClock); + + /* For Hybrid mode */ +// ptpClock->masterAddr = 0; + + ptpClock->maxDelayRejected = 0; + +} + +void +updateDelay(one_way_delay_filter * mpd_filt, const RunTimeOpts * rtOpts, PtpClock * ptpClock, TimeInternal * correctionField) +{ + + /* updates paused, leap second pending - do nothing */ + if(ptpClock->leapSecondInProgress) + return; + + DBGV("updateDelay\n"); + + /* todo: do all intermediate calculations on temp vars */ + TimeInternal prev_meanPathDelay = ptpClock->currentDS.meanPathDelay; + + ptpClock->char_last_msg = 'D'; + + Boolean maxDelayHit = FALSE; + + { + +#ifdef PTPD_STATISTICS + /* if maxDelayStableOnly configured, only check once servo is stable */ + Boolean checkThreshold = rtOpts-> maxDelayStableOnly ? + (ptpClock->servo.isStable && rtOpts->maxDelay) : + (rtOpts->maxDelay); +#else + Boolean checkThreshold = rtOpts->maxDelay; +#endif + //perform basic checks, using local variables only + TimeInternal slave_to_master_delay; + + + /* calc 'slave_to_master_delay' */ + subTime(&slave_to_master_delay, &ptpClock->delay_req_receive_time, + &ptpClock->delay_req_send_time); + + if (checkThreshold && /* If maxDelay is 0 then it's OFF */ + ptpClock->offsetFirstUpdated) { + + if ((slave_to_master_delay.nanoseconds < 0) && + (abs(slave_to_master_delay.nanoseconds) > rtOpts->maxDelay)) { + INFO("updateDelay aborted, " + "delay (sec: %d ns: %d) is negative\n", + slave_to_master_delay.seconds, + slave_to_master_delay.nanoseconds); + INFO("send (sec: %d ns: %d)\n", + ptpClock->delay_req_send_time.seconds, + ptpClock->delay_req_send_time.nanoseconds); + INFO("recv (sec: %d n s: %d)\n", + ptpClock->delay_req_receive_time.seconds, + ptpClock->delay_req_receive_time.nanoseconds); + goto finish; + } + + if (slave_to_master_delay.seconds && checkThreshold) { + INFO("updateDelay aborted, slave to master delay %d.%d greater than 1 second\n", + slave_to_master_delay.seconds, + slave_to_master_delay.nanoseconds); + if (rtOpts->displayPackets) + msgDump(ptpClock); + goto finish; + } + + if (slave_to_master_delay.nanoseconds > rtOpts->maxDelay) { + ptpClock->counters.maxDelayDrops++; + DBG("updateDelay aborted, slave to master delay %d greater than " + "administratively set maximum %d\n", + slave_to_master_delay.nanoseconds, + rtOpts->maxDelay); + if(rtOpts->maxDelayMaxRejected) { + maxDelayHit = TRUE; + /* if we blocked maxDelayMaxRejected samples, reset the slave to unblock the filter */ + if(++ptpClock->maxDelayRejected > rtOpts->maxDelayMaxRejected) { + WARNING("%d consecutive measurements above %d threshold - resetting slave\n", + rtOpts->maxDelayMaxRejected, slave_to_master_delay.nanoseconds); + toState(PTP_LISTENING, rtOpts, ptpClock); + } + } + + if (rtOpts->displayPackets) + msgDump(ptpClock); + goto finish; + } else { + ptpClock->maxDelayRejected=0; + } + } + } + + /* + * The packet has passed basic checks, so we'll: + * - update the global delaySM variable + * - calculate a new filtered MPD + */ + if (ptpClock->offsetFirstUpdated) { + Integer16 s; + + /* + * calc 'slave_to_master_delay' (Master to Slave delay is + * already computed in updateOffset ) + */ + + DBG("==> UpdateDelay(): %s\n", + dump_TimeInternal2("Req_RECV:", &ptpClock->delay_req_receive_time, + "Req_SENT:", &ptpClock->delay_req_send_time)); + + /* raw value before filtering */ + subTime(&ptpClock->rawDelaySM, &ptpClock->delay_req_receive_time, + &ptpClock->delay_req_send_time); + +#ifdef PTPD_STATISTICS + +/* testing only: step detection */ +#if 0 + TimeInternal bob; + bob.nanoseconds = -1000000; + bob.seconds = 0; + if(ptpClock->addOffset) { + addTime(&ptpClock->rawDelaySM, &ptpClock->rawDelaySM, &bob); + } +#endif + + /* run the delayMS stats filter */ + if(rtOpts->filterSMOpts.enabled) { + if(!feedDoubleMovingStatFilter(ptpClock->filterSM, timeInternalToDouble(&ptpClock->rawDelaySM))) { + return; + } + ptpClock->rawDelaySM = doubleToTimeInternal(ptpClock->filterSM->output); + } + + /* run the delaySM outlier filter */ + if(!rtOpts->noAdjust && ptpClock->oFilterSM.config.enabled && (ptpClock->oFilterSM.config.alwaysFilter || !ptpClock->servo.runningMaxOutput) ) { + if(ptpClock->oFilterSM.filter(&ptpClock->oFilterSM, timeInternalToDouble(&ptpClock->rawDelaySM))) { + ptpClock->delaySM = doubleToTimeInternal(ptpClock->oFilterSM.output); + } else { + ptpClock->counters.delaySMOutliersFound++; + /* If the outlier filter has blocked the sample, "reverse" the last maxDelay action */ + if (maxDelayHit) { + ptpClock->maxDelayRejected--; + } + goto finish; + } + } else { + ptpClock->delaySM = ptpClock->rawDelaySM; + } + + + +#else + subTime(&ptpClock->delaySM, &ptpClock->delay_req_receive_time, + &ptpClock->delay_req_send_time); +#endif + + /* update MeanPathDelay */ + addTime(&ptpClock->currentDS.meanPathDelay, &ptpClock->delaySM, + &ptpClock->delayMS); + + /* Subtract correctionField */ + subTime(&ptpClock->currentDS.meanPathDelay, &ptpClock->currentDS.meanPathDelay, + correctionField); + + /* Compute one-way delay */ + div2Time(&ptpClock->currentDS.meanPathDelay); + + if (ptpClock->currentDS.meanPathDelay.seconds) { + DBG("update delay: cannot filter with large OFM, " + "clearing filter\n"); + INFO("Servo: Ignoring delayResp because of large OFM\n"); + + mpd_filt->s_exp = mpd_filt->nsec_prev = 0; + /* revert back to previous value */ + ptpClock->currentDS.meanPathDelay = prev_meanPathDelay; + goto finish; + } + + if(ptpClock->currentDS.meanPathDelay.nanoseconds < 0){ + DBG("update delay: found negative value for OWD, " + "so ignoring this value: %d\n", + ptpClock->currentDS.meanPathDelay.nanoseconds); + /* revert back to previous value */ + ptpClock->currentDS.meanPathDelay = prev_meanPathDelay; + goto finish; + } + + /* avoid overflowing filter */ + s = rtOpts->s; + while (abs(mpd_filt->y) >> (31 - s)) + --s; + + /* crank down filter cutoff by increasing 's_exp' */ + if (mpd_filt->s_exp < 1) + mpd_filt->s_exp = 1; + else if (mpd_filt->s_exp < 1 << s) + ++mpd_filt->s_exp; + else if (mpd_filt->s_exp > 1 << s) + mpd_filt->s_exp = 1 << s; + + /* filter 'meanPathDelay' */ + double fy = + (double)((mpd_filt->s_exp - 1.0) * + mpd_filt->y / (mpd_filt->s_exp + 0.0) + + (ptpClock->currentDS.meanPathDelay.nanoseconds / 2.0 + + mpd_filt->nsec_prev / 2.0) / (mpd_filt->s_exp + 0.0)); + + mpd_filt->nsec_prev = ptpClock->currentDS.meanPathDelay.nanoseconds; + + mpd_filt->y = round(fy); + + ptpClock->currentDS.meanPathDelay.nanoseconds = mpd_filt->y; + + DBGV("delay filter %d, %d\n", mpd_filt->y, mpd_filt->s_exp); + } else { + DBG("Ignoring delayResp because we didn't receive any sync yet\n"); + ptpClock->counters.discardedMessages++; + } + +finish: + +DBG("UpdateDelay: Max delay hit: %d\n", maxDelayHit); + +#ifdef PTPD_STATISTICS + /* don't churn on stats containers with the old value if we've discarded an outlier */ + if(!(ptpClock->oFilterSM.config.enabled && ptpClock->oFilterSM.config.discard && ptpClock->oFilterSM.lastOutlier)) { + feedDoublePermanentStdDev(&ptpClock->slaveStats.mpdStats, timeInternalToDouble(&ptpClock->currentDS.meanPathDelay)); + feedDoublePermanentMedian(&ptpClock->slaveStats.mpdMedianContainer, timeInternalToDouble(&ptpClock->currentDS.meanPathDelay)); + if(!ptpClock->slaveStats.mpdStatsUpdated) { + if(timeInternalToDouble(&ptpClock->currentDS.meanPathDelay) != 0.0){ + ptpClock->slaveStats.mpdMax = timeInternalToDouble(&ptpClock->currentDS.meanPathDelay); + ptpClock->slaveStats.mpdMin = timeInternalToDouble(&ptpClock->currentDS.meanPathDelay); + ptpClock->slaveStats.mpdStatsUpdated = TRUE; + } + } else { + ptpClock->slaveStats.mpdMax = max(ptpClock->slaveStats.mpdMax, timeInternalToDouble(&ptpClock->currentDS.meanPathDelay)); + ptpClock->slaveStats.mpdMin = min(ptpClock->slaveStats.mpdMin, timeInternalToDouble(&ptpClock->currentDS.meanPathDelay)); + } + } +#endif /* PTPD_STATISTICS */ + + logStatistics(ptpClock); + +} + +void +updatePeerDelay(one_way_delay_filter * mpd_filt, const RunTimeOpts * rtOpts, PtpClock * ptpClock, TimeInternal * correctionField, Boolean twoStep) +{ + Integer16 s; + + /* updates paused, leap second pending - do nothing */ + if(ptpClock->leapSecondInProgress) + return; + + + DBGV("updatePeerDelay\n"); + + ptpClock->char_last_msg = 'P'; + + if (twoStep) { + /* calc 'slave_to_master_delay' */ + subTime(&ptpClock->pdelayMS, + &ptpClock->pdelay_resp_receive_time, + &ptpClock->pdelay_resp_send_time); + subTime(&ptpClock->pdelaySM, + &ptpClock->pdelay_req_receive_time, + &ptpClock->pdelay_req_send_time); + + /* update 'one_way_delay' */ + addTime(&ptpClock->portDS.peerMeanPathDelay, + &ptpClock->pdelayMS, + &ptpClock->pdelaySM); + + /* Subtract correctionField */ + subTime(&ptpClock->portDS.peerMeanPathDelay, + &ptpClock->portDS.peerMeanPathDelay, correctionField); + + /* Compute one-way delay */ + div2Time(&ptpClock->portDS.peerMeanPathDelay); + } else { + /* One step clock */ + + subTime(&ptpClock->portDS.peerMeanPathDelay, + &ptpClock->pdelay_resp_receive_time, + &ptpClock->pdelay_req_send_time); + + /* Subtract correctionField */ + subTime(&ptpClock->portDS.peerMeanPathDelay, + &ptpClock->portDS.peerMeanPathDelay, correctionField); + + /* Compute one-way delay */ + div2Time(&ptpClock->portDS.peerMeanPathDelay); + } + + if (ptpClock->portDS.peerMeanPathDelay.seconds) { + /* cannot filter with secs, clear filter */ + mpd_filt->s_exp = mpd_filt->nsec_prev = 0; + return; + } + /* avoid overflowing filter */ + s = rtOpts->s; + while (abs(mpd_filt->y) >> (31 - s)) + --s; + + /* crank down filter cutoff by increasing 's_exp' */ + if (mpd_filt->s_exp < 1) + mpd_filt->s_exp = 1; + else if (mpd_filt->s_exp < 1 << s) + ++mpd_filt->s_exp; + else if (mpd_filt->s_exp > 1 << s) + mpd_filt->s_exp = 1 << s; + + /* filter 'meanPathDelay' */ + mpd_filt->y = (mpd_filt->s_exp - 1) * + mpd_filt->y / mpd_filt->s_exp + + (ptpClock->portDS.peerMeanPathDelay.nanoseconds / 2 + + mpd_filt->nsec_prev / 2) / mpd_filt->s_exp; + + mpd_filt->nsec_prev = ptpClock->portDS.peerMeanPathDelay.nanoseconds; + ptpClock->portDS.peerMeanPathDelay.nanoseconds = mpd_filt->y; + + DBGV("delay filter %d, %d\n", mpd_filt->y, mpd_filt->s_exp); + + + if(ptpClock->portDS.portState == PTP_SLAVE) + logStatistics(ptpClock); +} + +void +updateOffset(TimeInternal * send_time, TimeInternal * recv_time, + offset_from_master_filter * ofm_filt, const RunTimeOpts * rtOpts, PtpClock * ptpClock, TimeInternal * correctionField) +{ + + ptpClock->clockControl.offsetOK = FALSE; + + Boolean maxDelayHit = FALSE; + + DBGV("UTCOffset: %d | leap 59: %d | leap61: %d\n", + ptpClock->timePropertiesDS.currentUtcOffset,ptpClock->timePropertiesDS.leap59,ptpClock->timePropertiesDS.leap61); + + /* prepare time constant for servo*/ + ptpClock->servo.maxdT = rtOpts->servoMaxdT; + if(ptpClock->portDS.logSyncInterval == UNICAST_MESSAGEINTERVAL) { + ptpClock->servo.dT = 1; + + if(rtOpts->unicastNegotiation && ptpClock->parentGrants && ptpClock->parentGrants->grantData[SYNC].granted) { + ptpClock->servo.dT = pow(2,ptpClock->parentGrants->grantData[SYNC].logInterval); + } + + } else { + ptpClock->servo.dT = pow(2, ptpClock->portDS.logSyncInterval); + } + +#ifdef PTPD_STATISTICS + /* multiply interval if interval filter used on delayMS */ + if(rtOpts->filterMSOpts.enabled && rtOpts->filterMSOpts.windowType != WINDOW_SLIDING) { + ptpClock->servo.dT *= rtOpts->filterMSOpts.windowSize; + } +#endif + /* updates paused, leap second pending - do nothing */ + if(ptpClock->leapSecondInProgress) + return; + + DBGV("==> updateOffset\n"); + + { + +#ifdef PTPD_STATISTICS + /* if maxDelayStableOnly configured, only check once servo is stable */ + Boolean checkThreshold = (rtOpts->maxDelayStableOnly ? + (ptpClock->servo.isStable && rtOpts->maxDelay) : + (rtOpts->maxDelay)); +#else + Boolean checkThreshold = rtOpts->maxDelay; +#endif + + //perform basic checks, using only local variables + TimeInternal master_to_slave_delay; + + + /* calc 'master_to_slave_delay' */ + subTime(&master_to_slave_delay, recv_time, send_time); + + if (checkThreshold) { /* If maxDelay is 0 then it's OFF */ + if (master_to_slave_delay.seconds && checkThreshold) { + INFO("updateOffset aborted, master to slave delay greater than 1" + " second.\n"); + /* msgDump(ptpClock); */ + return; + } + + if (abs(master_to_slave_delay.nanoseconds) > rtOpts->maxDelay) { + ptpClock->counters.maxDelayDrops++; + DBG("updateOffset aborted, master to slave delay %d greater than " + "administratively set maximum %d\n", + master_to_slave_delay.nanoseconds, + rtOpts->maxDelay); + if(rtOpts->maxDelayMaxRejected) { + maxDelayHit = TRUE; + /* if we blocked maxDelayMaxRejected samples, reset the slave to unblock the filter */ + if(++ptpClock->maxDelayRejected > rtOpts->maxDelayMaxRejected) { + WARNING("%d consecutive delay measurements above %d threshold - resetting slave\n", + rtOpts->maxDelayMaxRejected, master_to_slave_delay.nanoseconds); + toState(PTP_LISTENING, rtOpts, ptpClock); + } + } else { + ptpClock->maxDelayRejected=0; + } + if (rtOpts->displayPackets) + msgDump(ptpClock); + return; + } + }} + + // used for stats feedback + ptpClock->char_last_msg='S'; + + /* + * The packet has passed basic checks, so we'll: + * - run the filters + * - update the global delayMS variable + * - calculate the new filtered OFM + */ + + /* raw value before filtering */ + subTime(&ptpClock->rawDelayMS, recv_time, send_time); + +DBG("UpdateOffset: max delay hit: %d\n", maxDelayHit); + +#ifdef PTPD_STATISTICS + +/* testing only: step detection */ +/* + TimeInternal bob; + bob.nanoseconds = 1000000; + bob.seconds = 0; + if(ptpClock->addOffset) { + addTime(&ptpClock->rawDelayMS, &ptpClock->rawDelayMS, &bob); + } +*/ + /* run the delayMS stats filter */ + if(rtOpts->filterMSOpts.enabled) { + /* FALSE if filter wants to skip the update */ + if(!feedDoubleMovingStatFilter(ptpClock->filterMS, timeInternalToDouble(&ptpClock->rawDelayMS))) { + goto finish; + } + ptpClock->rawDelayMS = doubleToTimeInternal(ptpClock->filterMS->output); + } + + /* run the delayMS outlier filter */ + if(!rtOpts->noAdjust && ptpClock->oFilterMS.config.enabled && (ptpClock->oFilterMS.config.alwaysFilter || !ptpClock->servo.runningMaxOutput)) { + if(ptpClock->oFilterMS.filter(&ptpClock->oFilterMS, timeInternalToDouble(&ptpClock->rawDelayMS))) { + ptpClock->delayMS = doubleToTimeInternal(ptpClock->oFilterMS.output); + } else { + ptpClock->counters.delayMSOutliersFound++; + /* If the outlier filter has blocked the sample, "reverse" the last maxDelay action */ + if (maxDelayHit) { + ptpClock->maxDelayRejected--; + } + goto finish; + } + } else { + ptpClock->delayMS = ptpClock->rawDelayMS; + } +#else + /* Used just for End to End mode. */ + subTime(&ptpClock->delayMS, recv_time, send_time); +#endif + + /* Take care of correctionField */ + subTime(&ptpClock->delayMS, + &ptpClock->delayMS, correctionField); + + /* update 'offsetFromMaster' */ + if (ptpClock->portDS.delayMechanism == P2P) { + subTime(&ptpClock->currentDS.offsetFromMaster, + &ptpClock->delayMS, + &ptpClock->portDS.peerMeanPathDelay); + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + /* AUTOSAR mode enabled and DELAY_DISABLED -- use GlobalTimePropagationDelay */ + } else if ( ptpClock->portDS.delayMechanism == DELAY_DISABLED ) { + + TimeInternal staticPropagationDelay = doubleToTimeInternal( rtOpts->scoreConfig.GlobalTimePropagationDelay ); + + subTime(&ptpClock->currentDS.offsetFromMaster, + &ptpClock->delayMS, + &staticPropagationDelay); + +#endif + // ##### SCORE MODIFICATION END ##### + /* (End to End mode or disabled - if disabled, meanpath delay is zero) */ + } else if (ptpClock->portDS.delayMechanism == E2E || + ptpClock->portDS.delayMechanism == DELAY_DISABLED ) { + + subTime(&ptpClock->currentDS.offsetFromMaster, + &ptpClock->delayMS, + &ptpClock->currentDS.meanPathDelay); + } + + if (ptpClock->currentDS.offsetFromMaster.seconds) { + /* cannot filter with secs, clear filter */ + ofm_filt->nsec_prev = 0; + ptpClock->offsetFirstUpdated = TRUE; + ptpClock->clockControl.offsetOK = TRUE; + SET_ALARM(ALRM_OFM_SECONDS, TRUE); + goto finish; + } else { + SET_ALARM(ALRM_OFM_SECONDS, FALSE); + if(rtOpts->ofmAlarmThreshold) { + if( abs(ptpClock->currentDS.offsetFromMaster.nanoseconds) + > rtOpts->ofmAlarmThreshold) { + SET_ALARM(ALRM_OFM_THRESHOLD, TRUE); + } else { + SET_ALARM(ALRM_OFM_THRESHOLD, FALSE); + } + } + } + + /* filter 'offsetFromMaster' */ + ofm_filt->y = ptpClock->currentDS.offsetFromMaster.nanoseconds / 2 + + ofm_filt->nsec_prev / 2; + ofm_filt->nsec_prev = ptpClock->currentDS.offsetFromMaster.nanoseconds; + ptpClock->currentDS.offsetFromMaster.nanoseconds = ofm_filt->y; + + /* Apply the offset shift */ + subTime(&ptpClock->currentDS.offsetFromMaster, &ptpClock->currentDS.offsetFromMaster, + &rtOpts->ofmShift); + + DBGV("offset filter %d\n", ofm_filt->y); + + /* + * Offset must have been computed at least one time before + * computing end to end delay + */ + ptpClock->offsetFirstUpdated = TRUE; + ptpClock->clockControl.offsetOK = TRUE; + +#ifdef PTPD_STATISTICS + if(!ptpClock->oFilterMS.lastOutlier) { + feedDoublePermanentStdDev(&ptpClock->slaveStats.ofmStats, timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster)); + feedDoublePermanentMedian(&ptpClock->slaveStats.ofmMedianContainer, timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster)); + if(!ptpClock->slaveStats.ofmStatsUpdated) { + if(timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster) != 0.0){ + ptpClock->slaveStats.ofmMax = timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster); + ptpClock->slaveStats.ofmMin = timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster); + ptpClock->slaveStats.ofmStatsUpdated = TRUE; + } + } else { + ptpClock->slaveStats.ofmMax = max(ptpClock->slaveStats.ofmMax, timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster)); + ptpClock->slaveStats.ofmMin = min(ptpClock->slaveStats.ofmMin, timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster)); + } + + + } +#endif /* PTPD_STATISTICS */ + +finish: + logStatistics(ptpClock); + + DBGV("\n--Offset Correction-- \n"); + DBGV("Raw offset from master: %10ds %11dns\n", + ptpClock->delayMS.seconds, + ptpClock->delayMS.nanoseconds); + + DBGV("\n--Offset and Delay filtered-- \n"); + + if (ptpClock->portDS.delayMechanism == P2P) { + DBGV("one-way delay averaged (P2P): %10ds %11dns\n", + ptpClock->portDS.peerMeanPathDelay.seconds, + ptpClock->portDS.peerMeanPathDelay.nanoseconds); + } else if (ptpClock->portDS.delayMechanism == E2E) { + DBGV("one-way delay averaged (E2E): %10ds %11dns\n", + ptpClock->currentDS.meanPathDelay.seconds, + ptpClock->currentDS.meanPathDelay.nanoseconds); + } + + DBGV("offset from master: %10ds %11dns\n", + ptpClock->currentDS.offsetFromMaster.seconds, + ptpClock->currentDS.offsetFromMaster.nanoseconds); + DBGV("observed drift: %10d\n", ptpClock->servo.observedDrift); + +} + +void +stepClock(const RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + if(rtOpts->noAdjust){ + WARNING("Could not step clock - clock adjustment disabled\n"); + return; + } + + SET_ALARM(ALRM_CLOCK_STEP, TRUE); + + ptpClock->clockControl.stepRequired = FALSE; + + TimeInternal oldTime, newTime; + /*No need to reset the frequency offset: if we're far off, it will quickly get back to a high value */ + getTime(&oldTime); + subTime(&newTime, &oldTime, &ptpClock->currentDS.offsetFromMaster); + + setTime(&newTime); + + ptpClock->clockStatus.majorChange = TRUE; + + initClock(rtOpts, ptpClock); + + if(ptpClock->defaultDS.clockQuality.clockClass > 127) + restoreDrift(ptpClock, rtOpts, TRUE); + + ptpClock->servo.runningMaxOutput = FALSE; + toState(PTP_FAULTY, rtOpts, ptpClock); /* make a full protocol reset */ + + if(rtOpts->calibrationDelay) { + ptpClock->isCalibrated = FALSE; + } + +} + +void +warn_operator_fast_slewing(const RunTimeOpts * rtOpts, PtpClock * ptpClock, double adj) +{ + if(ptpClock->warned_operator_fast_slewing == 0){ + if ((adj >= rtOpts->servoMaxPpb) || ((adj <= -rtOpts->servoMaxPpb))){ + ptpClock->warned_operator_fast_slewing = 1; + NOTICE("Servo: Going to slew the clock with the maximum frequency adjustment\n"); + } + } + +} + +void +warn_operator_slow_slewing(const RunTimeOpts * rtOpts, PtpClock * ptpClock ) +{ + if(ptpClock->warned_operator_slow_slewing == 0){ + ptpClock->warned_operator_slow_slewing = 1; + ptpClock->warned_operator_fast_slewing = 1; + + /* rule of thumb: at tick rate 10000, slewing at the maximum speed took 0.5ms per second */ + float estimated = (((abs(ptpClock->currentDS.offsetFromMaster.seconds)) + 0.0) * 2.0 * 1000.0 / 3600.0); + + + ALERT("Servo: %d seconds offset detected, will take %.1f hours to slew\n", + ptpClock->currentDS.offsetFromMaster.seconds, + estimated + ); + + } +} + +/* + * this is a wrapper around adjFreq to abstract extra operations + */ + +void +adjFreq_wrapper(const RunTimeOpts * rtOpts, PtpClock * ptpClock, double adj) +{ + if (rtOpts->noAdjust){ + DBGV("adjFreq2: noAdjust on, returning\n"); + return; + } + +/* + * adjFreq simulation for QNX: correct clock by x ns per tick over clock adjust interval, + * to make it equal adj ns per second. Makes sense only if intervals are regular. + */ + +#ifdef __QNXNTO__ + + struct _clockadjust clockadj; + struct timespec ts_period; + // From QNX documentation: + // If you want to get the clock period, consider calling clock_getres() instead of using these kernel calls directly. + if( clock_getres (CLOCK_REALTIME, &ts_period) < 0 ) + return; + + CLAMP(adj,ptpClock->servo.maxOutput); + + /* adjust clock for the duration of 0.9 clock update period in ticks (so we're done before the next) */ + clockadj.tick_count = 0.9 * ptpClock->servo.dT * 1E9 / (ts_period.tv_nsec + 0.0); + + /* scale adjustment per second to adjustment per single tick */ + clockadj.tick_nsec_inc = (adj * ptpClock->servo.dT / clockadj.tick_count) / 0.9; + + DBGV("QNX: adj: %.09f, dt: %.09f, ticks per dt: %d, inc per tick %d\n", + adj, ptpClock->servo.dT, clockadj.tick_count, clockadj.tick_nsec_inc); + + if (ClockAdjust(CLOCK_REALTIME, &clockadj, NULL) < 0) { + DBGV("QNX: failed to call ClockAdjust: %s\n", strerror(errno)); + } +/* regular adjFreq */ +#elif defined(HAVE_SYS_TIMEX_H) + DBG2(" adjFreq2: call adjfreq to %.09f us \n", adj / DBG_UNIT); + adjFreq(adj); +/* otherwise use adjtime */ +#else + struct timeval tv; + + CLAMP(adj,ptpClock->servo.maxOutput); + + tv.tv_sec = 0; + tv.tv_usec = (adj / 1000); + if((ptpClock->servo.dT > 0) && (ptpClock->servo.dT < 1.0)) { + tv.tv_usec *= ptpClock->servo.dT; + } + adjtime(&tv, NULL); +#endif +} + +/* check if it's OK to update the clock, deal with panic mode, call for clock step */ +void checkOffset(const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + /* unless offset OK */ + ptpClock->clockControl.updateOK = FALSE; + + /* if false, updateOffset does not want us to continue */ + if(!ptpClock->clockControl.offsetOK) { + DBGV("checkOffset: !offsetOK\n"); + return; + } + + if(rtOpts->noAdjust) { + DBGV("checkOffset: noAdjust\n"); + /* in case if noAdjust has changed */ + ptpClock->clockControl.available = FALSE; + return; + } + + /* Leap second pending: do not update clock */ + if(ptpClock->leapSecondInProgress) { + /* let the watchdog know that we still want to hold the clock control */ + DBGV("checkOffset: leapSecondInProgress\n"); + ptpClock->clockControl.activity = TRUE; + return; + } + + /* check if we are allowed to step the clock */ + if(!ptpClock->pastStartup && ( + rtOpts->stepForce || (rtOpts->stepOnce && ptpClock->currentDS.offsetFromMaster.seconds) + )) { + if(rtOpts->stepForce) WARNING("First clock update - will step the clock\n"); + if(rtOpts->stepOnce) WARNING("First clock update and offset >= 1 second - will step the clock\n"); + ptpClock->clockControl.stepRequired = TRUE; + ptpClock->clockControl.updateOK = TRUE; + ptpClock->pastStartup = TRUE; + return; + } + + /* check if offset within allowed limit */ + if (rtOpts->maxOffset && ((abs(ptpClock->currentDS.offsetFromMaster.nanoseconds) > abs(rtOpts->maxOffset)) || + ptpClock->currentDS.offsetFromMaster.seconds)) { + INFO("Offset %d.%09d greater than " + "administratively set maximum %d\n. Will not update clock", + ptpClock->currentDS.offsetFromMaster.seconds, + ptpClock->currentDS.offsetFromMaster.nanoseconds, rtOpts->maxOffset); + return; + } + + /* offset above 1 second */ + if (ptpClock->currentDS.offsetFromMaster.seconds) { + + if(!rtOpts->enablePanicMode) { + if (!rtOpts->noResetClock) + CRITICAL("Offset above 1 second (%.09f s). Clock will step.\n", + timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster)); + ptpClock->clockControl.stepRequired = TRUE; + ptpClock->clockControl.updateOK = TRUE; + ptpClock->pastStartup = TRUE; + return; + } + + /* still in panic mode, do nothing */ + if(ptpClock->panicMode) { + DBG("checkOffset: still in panic mode\n"); + /* if we have not released clock control, keep the heartbeats going */ + if (ptpClock->clockControl.available) { + ptpClock->clockControl.activity = TRUE; + /* if for some reason we don't have clock control and not configured to release it, re-acquire it */ + } else if(!rtOpts->panicModeReleaseClock) { + ptpClock->clockControl.available = TRUE; + ptpClock->clockControl.activity = TRUE; + /* and vice versa - in case the setting has changed */ + } else { + ptpClock->clockControl.available = FALSE; + } + return; + } + + if(ptpClock->panicOver) { + if (rtOpts->noResetClock) + CRITICAL("Panic mode timeout - accepting current offset. Clock will be slewed at maximum rate.\n"); + else + CRITICAL("Panic mode timeout - accepting current offset. Clock will step.\n"); + ptpClock->panicOver = FALSE; + timerStop(&ptpClock->timers[PANIC_MODE_TIMER]); + ptpClock->clockControl.available = TRUE; + ptpClock->clockControl.stepRequired = TRUE; + ptpClock->clockControl.updateOK = TRUE; + ptpClock->pastStartup = TRUE; + ptpClock->isCalibrated = FALSE; + return; + } + + CRITICAL("Offset above 1 second (%.09f s) - entering panic mode. Clock updates paused.\n", + timeInternalToDouble(&ptpClock->currentDS.offsetFromMaster)); + ptpClock->panicMode = TRUE; + ptpClock->panicModeTimeLeft = 6 * rtOpts->panicModeDuration; + timerStart(&ptpClock->timers[PANIC_MODE_TIMER], 10); + /* do not release if not configured to do so */ + if(rtOpts->panicModeReleaseClock) { + ptpClock->clockControl.available = FALSE; + } + return; + + } + + /* offset below 1 second - exit panic mode if no threshold or if below threshold, + * but make sure we stayed in panic mode for at least one interval, + * so that we avoid flapping. + */ + if(rtOpts->enablePanicMode && ptpClock->panicMode && + (ptpClock->panicModeTimeLeft != (2 * rtOpts->panicModeDuration)) ) { + + if (rtOpts->panicModeExitThreshold == 0) { + ptpClock->panicMode = FALSE; + ptpClock->panicOver = FALSE; + timerStop(&ptpClock->timers[PANIC_MODE_TIMER]); + NOTICE("Offset below 1 second again: resuming clock control\n"); + /* we can control the clock again */ + ptpClock->clockControl.available = TRUE; + } else if ( abs(ptpClock->currentDS.offsetFromMaster.nanoseconds) < rtOpts->panicModeExitThreshold ) { + ptpClock->panicMode = FALSE; + ptpClock->panicOver = FALSE; + timerStop(&ptpClock->timers[PANIC_MODE_TIMER]); + NOTICE("Offset below %d ns threshold: resuming clock control\n", + ptpClock->currentDS.offsetFromMaster.nanoseconds); + /* we can control the clock again */ + ptpClock->clockControl.available = TRUE; + } + + } + + /* can this even happen if offset is < 1 sec? */ + if(rtOpts->enablePanicMode && ptpClock->panicOver) { + ptpClock->panicMode = FALSE; + ptpClock->panicOver = FALSE; + timerStop(&ptpClock->timers[PANIC_MODE_TIMER]); + NOTICE("Panic mode timeout and offset below 1 second again: resuming clock control\n"); + /* we can control the clock again */ + ptpClock->clockControl.available = TRUE; + } + + + + ptpClock->clockControl.updateOK = TRUE; + +} + +void +updateClock(const RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + + if(rtOpts->noAdjust) { + ptpClock->clockControl.available = FALSE; + DBGV("updateClock: noAdjust - skipped clock update\n"); + return; + } + + if(!ptpClock->clockControl.updateOK) { + DBGV("updateClock: !clockUpdateOK - skipped clock update\n"); + return; + } + + DBGV("==> updateClock\n"); + + if(ptpClock->clockControl.stepRequired) { + if (!rtOpts->noResetClock) { + stepClock(rtOpts, ptpClock); + ptpClock->clockControl.stepRequired = FALSE; + return; + } else { + if(ptpClock->currentDS.offsetFromMaster.nanoseconds > 0) + ptpClock->servo.observedDrift = rtOpts->servoMaxPpb; + else + ptpClock->servo.observedDrift = -rtOpts->servoMaxPpb; + warn_operator_slow_slewing(rtOpts, ptpClock); + adjFreq_wrapper(rtOpts, ptpClock, -ptpClock->servo.observedDrift); + ptpClock->clockControl.stepRequired = FALSE; + } + return; + } + + if (ptpClock->clockControl.granted) { + + /* only run the servo if we are calibrted - if calibration delay configured */ + + if((!rtOpts->calibrationDelay) || ptpClock->isCalibrated) { + NOTICE("[DEBUG] aptpd2: Clock adjusted\n"); + /* Adjust the clock first -> the PI controller runs here */ + adjFreq_wrapper(rtOpts, ptpClock, runPIservo(&ptpClock->servo, ptpClock->currentDS.offsetFromMaster.nanoseconds)); + } + warn_operator_fast_slewing(rtOpts, ptpClock, ptpClock->servo.observedDrift); + /* let the clock source know it's being synced */ + ptpClock->clockStatus.inSync = TRUE; + ptpClock->clockStatus.clockOffset = (ptpClock->currentDS.offsetFromMaster.seconds * 1E9 + + ptpClock->currentDS.offsetFromMaster.nanoseconds) / 1000; + ptpClock->clockStatus.update = TRUE; + } + + SET_ALARM(ALRM_FAST_ADJ, ptpClock->servo.runningMaxOutput); + + /* we are ready to control the clock */ + ptpClock->clockControl.available = TRUE; + ptpClock->clockControl.activity = TRUE; + + /* Clock has been updated - or was eligible for an update - restart the timeout timer*/ + if(rtOpts->clockUpdateTimeout > 0) { + DBG("Restarted clock update timeout timer\n"); + timerStart(&ptpClock->timers[CLOCK_UPDATE_TIMER],rtOpts->clockUpdateTimeout); + } + + ptpClock->pastStartup = TRUE; +#ifdef PTPD_STATISTICS + feedDoublePermanentStdDev(&ptpClock->servo.driftStats, ptpClock->servo.observedDrift); + feedDoublePermanentMedian(&ptpClock->servo.driftMedianContainer, ptpClock->servo.observedDrift); + if(!ptpClock->servo.statsUpdated) { + if(ptpClock->servo.observedDrift != 0.0){ + ptpClock->servo.driftMax = ptpClock->servo.observedDrift; + ptpClock->servo.driftMin = ptpClock->servo.observedDrift; + ptpClock->servo.statsUpdated = TRUE; + } + } else { + ptpClock->servo.driftMax = max(ptpClock->servo.driftMax, ptpClock->servo.observedDrift); + ptpClock->servo.driftMin = min(ptpClock->servo.driftMin, ptpClock->servo.observedDrift); + } +#endif + +} + +void +setupPIservo(PIservo* servo, const const RunTimeOpts* rtOpts) +{ + servo->maxOutput = rtOpts->servoMaxPpb; + servo->kP = rtOpts->servoKP; + servo->kI = rtOpts->servoKI; + servo->dTmethod = rtOpts->servoDtMethod; +#ifdef PTPD_STATISTICS + servo->stabilityThreshold = rtOpts->servoStabilityThreshold; + servo->stabilityPeriod = rtOpts->servoStabilityPeriod; + servo->stabilityTimeout = (60 / rtOpts->statsUpdateInterval) * rtOpts->servoStabilityTimeout; +#endif +} + +void +resetPIservo(PIservo* servo) +{ +/* not needed: restoreDrift handles this */ +/* servo->observedDrift = 0; */ + servo->input = 0; + servo->output = 0; + servo->lastUpdate.seconds = 0; + servo->lastUpdate.nanoseconds = 0; +} + +double +runPIservo(PIservo* servo, const Integer32 input) +{ + + double dt; + + TimeInternal now, delta; + + switch (servo->dTmethod) { + + case DT_MEASURED: + + getTimeMonotonic(&now); + if(servo->lastUpdate.seconds == 0 && + servo->lastUpdate.nanoseconds == 0) { + dt = servo->dT; + } else { + subTime(&delta, &now, &servo->lastUpdate); + dt = delta.seconds + delta.nanoseconds / 1E9; + } + + /* Don't use dT longer then max update interval multiplier */ + if(dt > (servo->maxdT * servo->dT)) + dt = (servo->maxdT + 0.0) * servo->dT; + + break; + + case DT_CONSTANT: + + dt = servo->dT; + + break; + + case DT_NONE: + default: + dt = 1.0; + break; + } + + if(dt <= 0.0) + dt = 1.0; + + servo->input = input; + + if (servo->kP < 0.000001) + servo->kP = 0.000001; + if (servo->kI < 0.000001) + servo->kI = 0.000001; + + servo->observedDrift += + dt * ((input + 0.0 ) * servo->kI); + + if(servo->observedDrift >= servo->maxOutput) { + servo->observedDrift = servo->maxOutput; + servo->runningMaxOutput = TRUE; +#ifdef PTPD_STATISTICS + servo->stableCount = 0; + servo->updateCount = 0; + servo->isStable = FALSE; +#endif /* PTPD_STATISTICS */ + } + else if(servo->observedDrift <= -servo->maxOutput) { + servo->observedDrift = -servo->maxOutput; + servo->runningMaxOutput = TRUE; +#ifdef PTPD_STATISTICS + servo->stableCount = 0; + servo->updateCount = 0; + servo->isStable = FALSE; +#endif /* PTPD_STATISTICS */ + } else { + servo->runningMaxOutput = FALSE; + } + + servo->output = (servo->kP * (input + 0.0) ) + servo->observedDrift; + + if(servo->dTmethod == DT_MEASURED) + servo->lastUpdate = now; + + DBGV("Servo dt: %.09f, input (ofm): %d, output(adj): %.09f, accumulator (observed drift): %.09f\n", dt, input, servo->output, servo->observedDrift); + + return -servo->output; + +} + +#ifdef PTPD_STATISTICS +static void +checkServoStable(PtpClock *ptpClock, const RunTimeOpts *rtOpts) +{ + + DBG("servo stablecount: %d\n",ptpClock->servo.stableCount); + + /* if not calibrated, do nothing */ + if( !rtOpts->calibrationDelay || ptpClock->isCalibrated ) { + ++ptpClock->servo.updateCount; + } else { + return; + } + + /* check if we're below the threshold or not */ + if(ptpClock->servo.runningMaxOutput || !ptpClock->acceptedUpdates || + (ptpClock->servo.driftStdDev > ptpClock->servo.stabilityThreshold)) { + ptpClock->servo.stableCount = 0; + } else if (ptpClock->servo.driftStdDev <= ptpClock->servo.stabilityThreshold) { + ptpClock->servo.stableCount++; + } + + /* Servo considered stable - drift std dev below threshold for n measurements - saving drift*/ + if(ptpClock->servo.stableCount >= ptpClock->servo.stabilityPeriod) { + + if(!ptpClock->servo.isStable) { + NOTICE("Clock servo now within stability threshold of %.03f ppb\n", + ptpClock->servo.stabilityThreshold); + } + + saveDrift(ptpClock, rtOpts, ptpClock->servo.isStable); + + ptpClock->servo.isStable = TRUE; + ptpClock->servo.stableCount = 0; + ptpClock->servo.updateCount = 0; + + /* servo not stable within max interval */ + } else if(ptpClock->servo.updateCount >= ptpClock->servo.stabilityTimeout) { + + ptpClock->servo.stableCount = 0; + ptpClock->servo.updateCount = 0; + + if(ptpClock->servo.isStable) { + WARNING("Clock servo outside stability threshold (%.03f ppb dev > %.03f ppb thr). Too many warnings may mean the threshold is too low.\n", + ptpClock->servo.driftStdDev, + ptpClock->servo.stabilityThreshold); + ptpClock->servo.isStable = FALSE; + } else { + if(ptpClock->servo.runningMaxOutput) { + WARNING("Clock servo outside stability threshold after %d seconds - running at maximum rate.\n", + rtOpts->statsUpdateInterval * ptpClock->servo.stabilityTimeout); + } else { + WARNING("Clock servo outside stability threshold %d seconds after last check. Saving current observed drift.\n", + rtOpts->statsUpdateInterval * ptpClock->servo.stabilityTimeout); + saveDrift(ptpClock, rtOpts, FALSE); + } + } + } + +} + +void +updatePtpEngineStats (PtpClock* ptpClock, const RunTimeOpts* rtOpts) +{ + + DBG("Refreshing slave engine stats counters\n"); + + DBG("samples used: %d/%d = %.03f\n", ptpClock->acceptedUpdates, ptpClock->offsetUpdates, (ptpClock->acceptedUpdates + 0.0) / (ptpClock->offsetUpdates + 0.0)); + + ptpClock->slaveStats.mpdMean = ptpClock->slaveStats.mpdStats.meanContainer.mean; + ptpClock->slaveStats.mpdStdDev = ptpClock->slaveStats.mpdStats.stdDev; + ptpClock->slaveStats.mpdMedian = ptpClock->slaveStats.mpdMedianContainer.median; + ptpClock->slaveStats.mpdMinFinal = ptpClock->slaveStats.mpdMin; + ptpClock->slaveStats.mpdMaxFinal = ptpClock->slaveStats.mpdMax; + ptpClock->slaveStats.ofmMean = ptpClock->slaveStats.ofmStats.meanContainer.mean; + ptpClock->slaveStats.ofmStdDev = ptpClock->slaveStats.ofmStats.stdDev; + ptpClock->slaveStats.ofmMedian = ptpClock->slaveStats.ofmMedianContainer.median; + ptpClock->slaveStats.ofmMinFinal = ptpClock->slaveStats.ofmMin; + ptpClock->slaveStats.ofmMaxFinal = ptpClock->slaveStats.ofmMax; + + ptpClock->slaveStats.statsCalculated = TRUE; + ptpClock->servo.driftMean = ptpClock->servo.driftStats.meanContainer.mean; + ptpClock->servo.driftStdDev = ptpClock->servo.driftStats.stdDev; + ptpClock->servo.driftMedian = ptpClock->servo.driftMedianContainer.median; + ptpClock->servo.statsCalculated = TRUE; + ptpClock->servo.driftMinFinal = ptpClock->servo.driftMin; + ptpClock->servo.driftMaxFinal = ptpClock->servo.driftMax; + + resetDoublePermanentMean(&ptpClock->oFilterMS.acceptedStats); + resetDoublePermanentMean(&ptpClock->oFilterSM.acceptedStats); + + if(ptpClock->slaveStats.mpdStats.meanContainer.count >= 10.0) { + resetDoublePermanentStdDev(&ptpClock->slaveStats.mpdStats); + resetDoublePermanentMedian(&ptpClock->slaveStats.mpdMedianContainer); + ptpClock->slaveStats.ofmStatsUpdated = FALSE; + ptpClock->slaveStats.mpdStatsUpdated = FALSE; + } + if(ptpClock->slaveStats.ofmStats.meanContainer.count >= 10.0) { + resetDoublePermanentStdDev(&ptpClock->slaveStats.ofmStats); + resetDoublePermanentMedian(&ptpClock->slaveStats.ofmMedianContainer); + } + if(ptpClock->servo.driftStats.meanContainer.count >= 10.0) + resetDoublePermanentStdDev(&ptpClock->servo.driftStats); + + resetDoublePermanentMedian(&ptpClock->servo.driftMedianContainer); + ptpClock->servo.statsUpdated = FALSE; + + if(rtOpts->servoStabilityDetection && ptpClock->clockControl.granted) { + checkServoStable(ptpClock, rtOpts); + } + + ptpClock->offsetUpdates = 0; + ptpClock->acceptedUpdates = 0; + +} + +#endif /* PTPD_STATISTICS */ diff --git a/src/ptpd/src/dep/snmp.c b/src/ptpd/src/dep/snmp.c new file mode 100644 index 0000000..ae15eaf --- /dev/null +++ b/src/ptpd/src/dep/snmp.c @@ -0,0 +1,2186 @@ +/*- + * Copyright (c) 2015 Wojciech Owczarek + * Copyright (c) 2012 The IMS Company + * Vincent Bernat + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file snmp.c + * @author Vincent Bernat + * @date Sat Jun 23 23:08:05 2012 + * + * @brief SNMP related functions + */ + +#include "../ptpd.h" + +#include +#include +#include + +//static void sendNotif(int event); +static void sendNotif(int eventType, PtpEventData *eventData); + +/* Hard to get header... */ +extern int header_generic(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + + +enum { + + /* PTPBASE-MIB core */ + PTPBASE_SYSTEM_PROFILE = 1, + PTPBASE_DOMAIN_CLOCK_PORTS_TOTAL, + PTPBASE_SYSTEM_DOMAIN_TOTALS, + PTPBASE_CLOCK_CURRENT_DS_STEPS_REMOVED, + PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER, + PTPBASE_CLOCK_CURRENT_DS_MEAN_PATH_DELAY, + PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ID, + PTPBASE_CLOCK_PARENT_DS_PARENT_STATS, + PTPBASE_CLOCK_PARENT_DS_OFFSET, + PTPBASE_CLOCK_PARENT_DS_CLOCK_PH_CH_RATE, + PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_IDENTITY, + PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_PRIO1, + PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_PRIO2, + PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_CLASS, + PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_ACCURACY, + PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_OFFSET, + PTPBASE_CLOCK_DEFAULT_DS_TWO_STEP_FLAG, + PTPBASE_CLOCK_DEFAULT_DS_CLOCK_IDENTITY, + PTPBASE_CLOCK_DEFAULT_DS_PRIO1, + PTPBASE_CLOCK_DEFAULT_DS_PRIO2, + PTPBASE_CLOCK_DEFAULT_DS_SLAVE_ONLY, + PTPBASE_CLOCK_DEFAULT_DS_QUALITY_CLASS, + PTPBASE_CLOCK_DEFAULT_DS_QUALITY_ACCURACY, + PTPBASE_CLOCK_DEFAULT_DS_QUALITY_OFFSET, + PTPBASE_CLOCK_DEFAULT_DS_DOMAIN_NUMBER, /* PTPd addition */ + PTPBASE_CLOCK_TIME_PROPERTIES_DS_CURRENT_UTC_OFFSET_VALID, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_CURRENT_UTC_OFFSET, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_LEAP59, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_LEAP61, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_TIME_TRACEABLE, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_FREQ_TRACEABLE, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_PTP_TIMESCALE, + PTPBASE_CLOCK_TIME_PROPERTIES_DS_SOURCE, + PTPBASE_CLOCK_PORT_NAME, + PTPBASE_CLOCK_PORT_ROLE, + PTPBASE_CLOCK_PORT_SYNC_ONE_STEP, + PTPBASE_CLOCK_PORT_CURRENT_PEER_ADDRESS_TYPE, + PTPBASE_CLOCK_PORT_CURRENT_PEER_ADDRESS, + PTPBASE_CLOCK_PORT_NUM_ASSOCIATED_PORTS, + PTPBASE_CLOCK_PORT_DS_PORT_NAME, + PTPBASE_CLOCK_PORT_DS_PORT_IDENTITY, + PTPBASE_CLOCK_PORT_DS_ANNOUNCEMENT_INTERVAL, + PTPBASE_CLOCK_PORT_DS_ANNOUNCE_RCT_TIMEOUT, + PTPBASE_CLOCK_PORT_DS_SYNC_INTERVAL, + PTPBASE_CLOCK_PORT_DS_MIN_DELAY_REQ_INTERVAL, + PTPBASE_CLOCK_PORT_DS_PEER_DELAY_REQ_INTERVAL, + PTPBASE_CLOCK_PORT_DS_DELAY_MECH, + PTPBASE_CLOCK_PORT_DS_PEER_MEAN_PATH_DELAY, + PTPBASE_CLOCK_PORT_DS_GRANT_DURATION, + PTPBASE_CLOCK_PORT_DS_PTP_VERSION, + PTPBASE_CLOCK_PORT_DS_PEER_MEAN_PATH_DELAY_STRING, /* PTPd addition */ + PTPBASE_CLOCK_PORT_DS_LAST_MISMATCHED_DOMAIN, /* PTPd addition */ + PTPBASE_CLOCK_PORT_RUNNING_NAME, + PTPBASE_CLOCK_PORT_RUNNING_STATE, + PTPBASE_CLOCK_PORT_RUNNING_ROLE, + PTPBASE_CLOCK_PORT_RUNNING_INTERFACE_INDEX, + PTPBASE_CLOCK_PORT_RUNNING_IPVERSION, + PTPBASE_CLOCK_PORT_RUNNING_ENCAPSULATION_TYPE, + PTPBASE_CLOCK_PORT_RUNNING_TX_MODE, + PTPBASE_CLOCK_PORT_RUNNING_RX_MODE, + PTPBASE_CLOCK_PORT_RUNNING_PACKETS_RECEIVED, + PTPBASE_CLOCK_PORT_RUNNING_PACKETS_SENT, + + /* PTPd additions to PTPBASE-MIB */ + PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER_STRING, + PTPBASE_CLOCK_CURRENT_DS_MEAN_PATH_DELAY_STRING, + PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER_THRESHOLD, + + PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ADDRESS_TYPE, + PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ADDRESS, + + /* ptpbasePtpPortMessageCounters */ + PTPBASE_PORT_MESSAGE_COUNTERS_CLEAR, + PTPBASE_PORT_MESSAGE_COUNTERS_CLEAR_ALL, + PTPBASE_PORT_MESSAGE_COUNTERS_TOTAL_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_TOTAL_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_ANNOUNCE_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_ANNOUNCE_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_SYNC_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_SYNC_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_FOLLOWUP_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_FOLLOWUP_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_DELAYREQ_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_DELAYREQ_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_DELAYRESP_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_DELAYRESP_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYREQ_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYREQ_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_FOLLOWUP_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_FOLLOWUP_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_SIGNALING_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_SIGNALING_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_MANAGEMENT_SENT, + PTPBASE_PORT_MESSAGE_COUNTERS_MANAGEMENT_RECEIVED, + PTPBASE_PORT_MESSAGE_COUNTERS_DISCARDED_MESSAGES, + PTPBASE_PORT_MESSAGE_COUNTERS_UNKNOWN_MESSAGES, + /* ptpBasePtpProtocolCounters */ + PTPBASE_PORT_PROTOCOL_COUNTERS_CLEAR, + PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_ADDED, + PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_COUNT, + PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_REMOVED, + PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_OVERFLOWS, + PTPBASE_PORT_PROTOCOL_COUNTERS_STATE_TRANSITIONS, + PTPBASE_PORT_PROTOCOL_COUNTERS_BEST_MASTER_CHANGES, + PTPBASE_PORT_PROTOCOL_COUNTERS_ANNOUNCE_TIMEOUTS, + /* ptpBasePtpErrorCounters */ + PTPBASE_PORT_ERROR_COUNTERS_CLEAR, + PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_RECV, + PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_SEND, + PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_FORMAT, + PTPBASE_PORT_ERROR_COUNTERS_PROTOCOL, + PTPBASE_PORT_ERROR_COUNTERS_VERSION_MISMATCH, + PTPBASE_PORT_ERROR_COUNTERS_DOMAIN_MISMATCH, + PTPBASE_PORT_ERROR_COUNTERS_SEQUENCE_MISMATCH, + PTPBASE_PORT_ERROR_COUNTERS_DELAYMECH_MISMATCH, + /* ptpBasePtpUnicastNegotiationCounters */ + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_CLEAR, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_REQUESTED, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_GRANTED, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_DENIED, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_SENT, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_RECEIVED, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_ACK_SENT, + PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_ACK_RECEIVED, + /* ptpBasePtpPerformanceCounters */ + PTPBASE_PORT_PERFORMANCE_COUNTERS_MESSAGE_SEND_RATE, + PTPBASE_PORT_PERFORMANCE_COUNTERS_MESSAGE_RECEIVE_RATE, + /* ptpBasePtpSecurityCounters */ + PTPBASE_PORT_SECURITY_COUNTERS_CLEAR, + PTPBASE_PORT_SECURITY_COUNTERS_TIMING_ACL_DISCARDED, + PTPBASE_PORT_SECURITY_COUNTERS_MANAGEMENT_ACL_DISCARDED, + /* ptpBaseSlaveOfmStatistics */ + PTPBASE_SLAVE_OFM_STATS_CURRENT_VALUE, + PTPBASE_SLAVE_OFM_STATS_CURRENT_VALUE_STRING, + PTPBASE_SLAVE_OFM_STATS_PERIOD_SECONDS, + PTPBASE_SLAVE_OFM_STATS_VALID, + PTPBASE_SLAVE_OFM_STATS_MIN, + PTPBASE_SLAVE_OFM_STATS_MAX, + PTPBASE_SLAVE_OFM_STATS_MEAN, + PTPBASE_SLAVE_OFM_STATS_STDDEV, + PTPBASE_SLAVE_OFM_STATS_MEDIAN, + PTPBASE_SLAVE_OFM_STATS_MIN_STRING, + PTPBASE_SLAVE_OFM_STATS_MAX_STRING, + PTPBASE_SLAVE_OFM_STATS_MEAN_STRING, + PTPBASE_SLAVE_OFM_STATS_STDDEV_STRING, + PTPBASE_SLAVE_OFM_STATS_MEDIAN_STRING, + /* ptpBaseSlaveMpdStatistics */ + PTPBASE_SLAVE_MPD_STATS_CURRENT_VALUE, + PTPBASE_SLAVE_MPD_STATS_CURRENT_VALUE_STRING, + PTPBASE_SLAVE_MPD_STATS_PERIOD_SECONDS, + PTPBASE_SLAVE_MPD_STATS_VALID, + PTPBASE_SLAVE_MPD_STATS_MIN, + PTPBASE_SLAVE_MPD_STATS_MAX, + PTPBASE_SLAVE_MPD_STATS_MEAN, + PTPBASE_SLAVE_MPD_STATS_STDDEV, + PTPBASE_SLAVE_MPD_STATS_MEDIAN, + PTPBASE_SLAVE_MPD_STATS_MIN_STRING, + PTPBASE_SLAVE_MPD_STATS_MAX_STRING, + PTPBASE_SLAVE_MPD_STATS_MEAN_STRING, + PTPBASE_SLAVE_MPD_STATS_STDDEV_STRING, + PTPBASE_SLAVE_MPD_STATS_MEDIAN_STRING, + /* ptpBaseSlaveFreqAdjStatistics */ + PTPBASE_SLAVE_FREQADJ_STATS_CURRENT_VALUE, + PTPBASE_SLAVE_FREQADJ_STATS_PERIOD_SECONDS, + PTPBASE_SLAVE_FREQADJ_STATS_VALID, + PTPBASE_SLAVE_FREQADJ_STATS_MIN, + PTPBASE_SLAVE_FREQADJ_STATS_MAX, + PTPBASE_SLAVE_FREQADJ_STATS_MEAN, + PTPBASE_SLAVE_FREQADJ_STATS_STDDEV, + PTPBASE_SLAVE_FREQADJ_STATS_MEDIAN, + /* ptpBasePtpdSpecificCounters */ + PTPBASE_PTPD_SPECIFIC_COUNTERS_CLEAR, + PTPBASE_PTPD_SPECIFIC_COUNTERS_IGNORED_ANNOUNCE, + PTPBASE_PTPD_SPECIFIC_COUNTERS_CONSECUTIVE_SEQUENCE_ERRORS, + PTPBASE_PTPD_SPECIFIC_COUNTERS_DELAYMS_OUTLIERS_FOUND, + PTPBASE_PTPD_SPECIFIC_COUNTERS_DELAYSM_OUTLIERS_FOUND, + PTPBASE_PTPD_SPECIFIC_COUNTERS_MAX_DELAY_DROPS, + /* ptpBasePtpdSpecificData */ + PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYMS, + PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYMS_STRING, + PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYSM, + PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYSM_STRING +}; + +/* trap / notification definitions */ +enum { + PTPBASE_NOTIFS_EXPECTED_PORT_STATE, + PTPBASE_NOTIFS_UNEXPECTED_PORT_STATE, + PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_EXCEEDED, + PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_ACCEPTABLE, + PTPBASE_NOTIFS_SLAVE_CLOCK_STEP, + PTPBASE_NOTIFS_SLAVE_NO_SYNC, + PTPBASE_NOTIFS_SLAVE_RECEIVING_SYNC, + PTPBASE_NOTIFS_SLAVE_NO_DELAY, + PTPBASE_NOTIFS_SLAVE_RECEIVING_DELAY, + PTPBASE_NOTIFS_BEST_MASTER_CHANGE, + PTPBASE_NOTIFS_NETWORK_FAULT, + PTPBASE_NOTIFS_NETWORK_FAULT_CLEARED, + PTPBASE_NOTIFS_FREQADJ_FAST, + PTPBASE_NOTIFS_FREQADJ_NORMAL, + PTPBASE_NOTIFS_OFFSET_SECONDS, + PTPBASE_NOTIFS_OFFSET_SUB_SECONDS, + PTPBASE_NOTIFS_TIMEPROPERTIESDS_CHANGE, + PTPBASE_NOTIFS_DOMAIN_MISMATCH, + PTPBASE_NOTIFS_DOMAIN_MISMATCH_CLEARED, +}; + +#define SNMP_PTP_ORDINARY_CLOCK 1 +#define SNMP_PTP_CLOCK_INSTANCE 1 /* Only one instance */ +#define SNMP_PTP_PORT_MASTER 1 +#define SNMP_PTP_PORT_SLAVE 2 +#define SNMP_IPv4 1 +#define SNMP_PTP_TX_UNICAST 1 +#define SNMP_PTP_TX_MULTICAST 2 +#define SNMP_PTP_TX_MULTICAST_MIX 3 + +#define TRUTHVALUE_TRUE 1 +#define TRUTHVALUE_FALSE 2 + +#define TO_TRUTHVALUE(a) \ + (a ? TRUTHVALUE_TRUE : TRUTHVALUE_FALSE) + +#define PTPBASE_MIB_OID \ + 1, 3, 6, 1, 4, 1, 46649, 1, 1 +#define PTPBASE_MIB_INDEX2 \ + snmpPtpClock->defaultDS.domainNumber, SNMP_PTP_CLOCK_INSTANCE +#define PTPBASE_MIB_INDEX3 \ + snmpPtpClock->defaultDS.domainNumber, SNMP_PTP_ORDINARY_CLOCK, SNMP_PTP_CLOCK_INSTANCE +#define PTPBASE_MIB_INDEX4 \ + snmpPtpClock->defaultDS.domainNumber, SNMP_PTP_ORDINARY_CLOCK, SNMP_PTP_CLOCK_INSTANCE, snmpPtpClock->portDS.portIdentity.portNumber + +static oid ptp_oid[] = { PTPBASE_MIB_OID }; + +static PtpClock *snmpPtpClock; +static RunTimeOpts *snmpRtOpts; + +/* Helper functions to build header_*indexed_table() functions. Those + functions keep an internal state. They are not reentrant! +*/ +/* {{{ */ +struct snmpHeaderIndex { + struct variable *vp; + oid *name; /* Requested/returned OID */ + size_t *length; /* Length of above OID */ + int exact; + oid best[MAX_OID_LEN]; /* Best OID */ + size_t best_len; /* Best OID length */ + void *entity; /* Best entity */ +}; + +static int +snmpHeaderInit(struct snmpHeaderIndex *idx, + struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + /* If the requested OID name is less than OID prefix we + handle, adjust it to our prefix. */ + if ((snmp_oid_compare(name, *length, vp->name, vp->namelen)) < 0) { + memcpy(name, vp->name, sizeof(oid) * vp->namelen); + *length = vp->namelen; + } + /* Now, we can only handle OID matching our prefix. Those two + tests are not really necessary since NetSNMP won't give us + OID "above" our prefix. But this makes unit tests + easier. */ + if (*length < vp->namelen) return 0; + if (memcmp(name, vp->name, vp->namelen * sizeof(oid))) return 0; + + if(write_method != NULL) *write_method = 0; + *var_len = sizeof(long); + + /* Initialize our header index structure */ + idx->vp = vp; + idx->name = name; + idx->length = length; + idx->exact = exact; + idx->best_len = 0; + idx->entity = NULL; + return 1; +} + +static void +snmpHeaderIndexAdd(struct snmpHeaderIndex *idx, + oid *index, size_t len, void *entity) +{ + int result; + oid *target; + size_t target_len; + + target = idx->name + idx->vp->namelen; + target_len = *idx->length - idx->vp->namelen; + if ((result = snmp_oid_compare(index, len, target, target_len)) < 0) + return; /* Too small. */ + if (idx->exact) { + if (result == 0) { + memcpy(idx->best, index, sizeof(oid) * len); + idx->best_len = len; + idx->entity = entity; + return; + } + return; + } + if (result == 0) + return; /* Too small. */ + if (idx->best_len == 0 || + (snmp_oid_compare(index, len, + idx->best, + idx->best_len) < 0)) { + memcpy(idx->best, index, sizeof(oid) * len); + idx->best_len = len; + idx->entity = entity; + } +} + +static void* +snmpHeaderIndexBest(struct snmpHeaderIndex *idx) +{ + if (idx->entity == NULL) + return NULL; + if (idx->exact) { + if (snmp_oid_compare(idx->name + idx->vp->namelen, + *idx->length - idx->vp->namelen, + idx->best, idx->best_len) == 0) + return idx->entity; + return NULL; + } + memcpy(idx->name + idx->vp->namelen, + idx->best, sizeof(oid) * idx->best_len); + *idx->length = idx->vp->namelen + idx->best_len; + return idx->entity; +} +/* }}} */ + + +#define SNMP_LOCAL_VARIABLES \ + static unsigned long long_ret; \ + static struct counter64 counter64_ret; \ + static uint32_t ipaddr; \ + Integer32 i32_ret; \ + Integer64 bigint; \ + struct snmpHeaderIndex idx; \ + static char tmpStr[64]; \ + (void)long_ret; \ + (void)counter64_ret; \ + (void)ipaddr; \ + (void)bigint; \ + (void)i32_ret; \ + (void)tmpStr; \ + (void)idx +#define SNMP_INDEXED_TABLE \ + if (!snmpHeaderInit(&idx, vp, name, \ + length, exact, \ + var_len, \ + write_method)) \ + return NULL +#define SNMP_ADD_INDEX(index, len, w) \ + snmpHeaderIndexAdd(&idx, index, len, w) +#define SNMP_BEST_MATCH \ + snmpHeaderIndexBest(&idx) +#define SNMP_OCTETSTR(V, L) \ + ( *var_len = L, \ + (u_char *)V ) +#define SNMP_COUNTER64(V) \ + ( counter64_ret.low = (V) & 0xffffffff, \ + counter64_ret.high = (V) >> 32, \ + *var_len = sizeof (counter64_ret), \ + (u_char*)&counter64_ret ) +#define SNMP_TIMEINTERNAL(V) \ + ( *var_len = sizeof(counter64_ret), \ + internalTime_to_integer64(V, &bigint), \ + counter64_ret.low = htonl(bigint.lsb), \ + counter64_ret.high = htonl(bigint.msb), \ + (u_char *)&counter64_ret ) +#define SNMP_INTEGER(V) \ + ( long_ret = (V), \ + *var_len = sizeof (long_ret), \ + (u_char*)&long_ret ) +#define SNMP_IPADDR(A) \ + ( ipaddr = A, \ + *var_len = sizeof (ipaddr), \ + (u_char*)&ipaddr ) +#define SNMP_GAUGE SNMP_INTEGER +#define SNMP_UNSIGNED SNMP_INTEGER +#define SNMP_TRUE SNMP_INTEGER(1) +#define SNMP_FALSE SNMP_INTEGER(2) +#define SNMP_BOOLEAN(V) \ + (V == TRUE)?SNMP_TRUE:SNMP_FALSE +#define SNMP_SIGNATURE struct variable *vp, \ + oid *name, \ + size_t *length, \ + int exact, \ + size_t *var_len, \ + WriteMethod **write_method + +/** + * Handle SNMP scalar values. + */ +static u_char* +snmpScalars(SNMP_SIGNATURE) { + SNMP_LOCAL_VARIABLES; + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + switch (vp->magic) { + case PTPBASE_SYSTEM_PROFILE: + return SNMP_INTEGER(1); + } + + return NULL; +} + +/** + * Handle ptpbaseSystemTable + */ +static u_char* +snmpSystemTable(SNMP_SIGNATURE) { + oid index[2]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one index: one domain, one instance */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_CLOCK_INSTANCE; + SNMP_ADD_INDEX(index, 2, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_DOMAIN_CLOCK_PORTS_TOTAL: + return SNMP_GAUGE(snmpPtpClock->defaultDS.numberPorts); + } + + return NULL; +} + +/** + * Handle ptpbaseSystemDomainTable + */ +static u_char* +snmpSystemDomainTable(SNMP_SIGNATURE) { + oid index[1]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one index: ordinary clock */ + index[0] = SNMP_PTP_ORDINARY_CLOCK; + SNMP_ADD_INDEX(index, 1, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_SYSTEM_DOMAIN_TOTALS: + /* We only handle one domain... */ + return SNMP_UNSIGNED(1); + } + + return NULL; +} + +/** + * Handle various ptpbaseClock*DSTable + */ +static u_char* +snmpClockDSTable(SNMP_SIGNATURE) { + oid index[3]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + memset(tmpStr, 0, sizeof(tmpStr)); + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + SNMP_ADD_INDEX(index, 3, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + /* ptpbaseClockCurrentDSTable */ + case PTPBASE_CLOCK_CURRENT_DS_STEPS_REMOVED: + return SNMP_UNSIGNED(snmpPtpClock->currentDS.stepsRemoved); + case PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER: + return SNMP_TIMEINTERNAL(snmpPtpClock->currentDS.offsetFromMaster); + case PTPBASE_CLOCK_CURRENT_DS_MEAN_PATH_DELAY: + return SNMP_TIMEINTERNAL(snmpPtpClock->currentDS.meanPathDelay); + /* PTPd: offsets as string */ + case PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->currentDS.offsetFromMaster)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_CLOCK_CURRENT_DS_MEAN_PATH_DELAY_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->currentDS.meanPathDelay)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER_THRESHOLD: + return SNMP_INTEGER(snmpRtOpts->ofmAlarmThreshold); + + /* ptpbaseClockParentDSTable */ + case PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ID: + return SNMP_OCTETSTR(&snmpPtpClock->parentDS.parentPortIdentity, + sizeof(PortIdentity)); + case PTPBASE_CLOCK_PARENT_DS_PARENT_STATS: + return SNMP_BOOLEAN(snmpPtpClock->parentDS.parentStats); + case PTPBASE_CLOCK_PARENT_DS_OFFSET: + return SNMP_INTEGER(snmpPtpClock->parentDS.observedParentOffsetScaledLogVariance); + case PTPBASE_CLOCK_PARENT_DS_CLOCK_PH_CH_RATE: + return SNMP_INTEGER(snmpPtpClock->parentDS.observedParentClockPhaseChangeRate); + case PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_IDENTITY: + return SNMP_OCTETSTR(&snmpPtpClock->parentDS.grandmasterIdentity, + sizeof(ClockIdentity)); + case PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_PRIO1: + return SNMP_UNSIGNED(snmpPtpClock->parentDS.grandmasterPriority1); + case PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_PRIO2: + return SNMP_UNSIGNED(snmpPtpClock->parentDS.grandmasterPriority2); + case PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_CLASS: + return SNMP_UNSIGNED(snmpPtpClock->parentDS.grandmasterClockQuality.clockClass); + case PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_ACCURACY: + return SNMP_INTEGER(snmpPtpClock->parentDS.grandmasterClockQuality.clockAccuracy); + case PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_OFFSET: + return SNMP_INTEGER(snmpPtpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance); + /* PTPd addition */ + case PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ADDRESS_TYPE: + /* Only supports IPv4 */ + return SNMP_INTEGER(SNMP_IPv4); + case PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ADDRESS: + if(snmpRtOpts->transport != UDP_IPV4) + return SNMP_IPADDR(0); + if(!snmpPtpClock->bestMaster) + return SNMP_IPADDR(0); + return SNMP_IPADDR(snmpPtpClock->bestMaster->sourceAddr); + /* ptpbaseClockDefaultDSTable */ + case PTPBASE_CLOCK_DEFAULT_DS_TWO_STEP_FLAG: + return SNMP_BOOLEAN(snmpPtpClock->defaultDS.twoStepFlag); + case PTPBASE_CLOCK_DEFAULT_DS_CLOCK_IDENTITY: + return SNMP_OCTETSTR(&snmpPtpClock->defaultDS.clockIdentity, + sizeof(ClockIdentity)); + case PTPBASE_CLOCK_DEFAULT_DS_PRIO1: + return SNMP_UNSIGNED(snmpPtpClock->defaultDS.priority1); + case PTPBASE_CLOCK_DEFAULT_DS_PRIO2: + return SNMP_UNSIGNED(snmpPtpClock->defaultDS.priority2); + case PTPBASE_CLOCK_DEFAULT_DS_SLAVE_ONLY: + return SNMP_BOOLEAN(snmpPtpClock->defaultDS.slaveOnly); + case PTPBASE_CLOCK_DEFAULT_DS_QUALITY_CLASS: + return SNMP_UNSIGNED(snmpPtpClock->defaultDS.clockQuality.clockClass); + case PTPBASE_CLOCK_DEFAULT_DS_QUALITY_ACCURACY: + return SNMP_INTEGER(snmpPtpClock->defaultDS.clockQuality.clockAccuracy); + case PTPBASE_CLOCK_DEFAULT_DS_QUALITY_OFFSET: + return SNMP_INTEGER(snmpPtpClock->defaultDS.clockQuality.offsetScaledLogVariance); + /* PTPd addition */ + case PTPBASE_CLOCK_DEFAULT_DS_DOMAIN_NUMBER: + return SNMP_INTEGER(snmpPtpClock->defaultDS.domainNumber); + /* ptpbaseClockTimePropertiesDSTable */ + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_CURRENT_UTC_OFFSET_VALID: + return SNMP_BOOLEAN(snmpPtpClock->timePropertiesDS.currentUtcOffsetValid); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_CURRENT_UTC_OFFSET: + return SNMP_INTEGER(snmpPtpClock->timePropertiesDS.currentUtcOffset); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_LEAP59: + return SNMP_BOOLEAN(snmpPtpClock->timePropertiesDS.leap59); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_LEAP61: + return SNMP_BOOLEAN(snmpPtpClock->timePropertiesDS.leap61); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_TIME_TRACEABLE: + return SNMP_BOOLEAN(snmpPtpClock->timePropertiesDS.timeTraceable); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_FREQ_TRACEABLE: + return SNMP_BOOLEAN(snmpPtpClock->timePropertiesDS.frequencyTraceable); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_PTP_TIMESCALE: + return SNMP_BOOLEAN(snmpPtpClock->timePropertiesDS.ptpTimescale); + case PTPBASE_CLOCK_TIME_PROPERTIES_DS_SOURCE: + return SNMP_INTEGER(snmpPtpClock->timePropertiesDS.timeSource); + } + + return NULL; +} + +/** + * Handle ptpbaseClockPort*Table + */ +static u_char* +snmpClockPortTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + /* ptpbaseClockPortTable */ + case PTPBASE_CLOCK_PORT_NAME: + case PTPBASE_CLOCK_PORT_DS_PORT_NAME: + case PTPBASE_CLOCK_PORT_RUNNING_NAME: + return SNMP_OCTETSTR(snmpPtpClock->userDescription, + strlen(snmpPtpClock->userDescription)); + case PTPBASE_CLOCK_PORT_ROLE: + return SNMP_INTEGER((snmpPtpClock->portDS.portState == PTP_MASTER)? + SNMP_PTP_PORT_MASTER:SNMP_PTP_PORT_SLAVE); + case PTPBASE_CLOCK_PORT_SYNC_ONE_STEP: + return (snmpPtpClock->defaultDS.twoStepFlag == TRUE)?SNMP_FALSE:SNMP_TRUE; + case PTPBASE_CLOCK_PORT_CURRENT_PEER_ADDRESS_TYPE: + /* Only supports IPv4 */ + return SNMP_INTEGER(SNMP_IPv4); + case PTPBASE_CLOCK_PORT_CURRENT_PEER_ADDRESS: + if(snmpRtOpts->transport != UDP_IPV4) + return SNMP_IPADDR(0); + return(SNMP_IPADDR(snmpPtpClock->netPath.interfaceAddr.s_addr)); + case PTPBASE_CLOCK_PORT_NUM_ASSOCIATED_PORTS: + if(snmpPtpClock->portDS.portState == PTP_MASTER && snmpRtOpts->unicastNegotiation) { + return SNMP_INTEGER(snmpPtpClock->slaveCount); + } + if(snmpPtpClock->portDS.portState == PTP_MASTER && snmpPtpClock->unicastDestinationCount) { + return SNMP_INTEGER(snmpPtpClock->unicastDestinationCount); + } + if(snmpPtpClock->portDS.portState == PTP_SLAVE) { + return SNMP_INTEGER(snmpPtpClock->number_foreign_records); + } + return SNMP_INTEGER(0); + /* ptpbaseClockPortDSTable */ + case PTPBASE_CLOCK_PORT_DS_PORT_IDENTITY: + return SNMP_OCTETSTR(&snmpPtpClock->portDS.portIdentity, + sizeof(PortIdentity)); + case PTPBASE_CLOCK_PORT_DS_ANNOUNCEMENT_INTERVAL: + /* TODO: is it really logAnnounceInterval? */ + return SNMP_INTEGER(snmpPtpClock->portDS.logAnnounceInterval); + case PTPBASE_CLOCK_PORT_DS_ANNOUNCE_RCT_TIMEOUT: + return SNMP_INTEGER(snmpPtpClock->portDS.announceReceiptTimeout); + case PTPBASE_CLOCK_PORT_DS_SYNC_INTERVAL: + /* TODO: is it really logSyncInterval? */ + return SNMP_INTEGER(snmpPtpClock->portDS.logSyncInterval); + case PTPBASE_CLOCK_PORT_DS_MIN_DELAY_REQ_INTERVAL: + /* TODO: is it really logMinDelayReqInterval? */ + return SNMP_INTEGER(snmpPtpClock->portDS.logMinDelayReqInterval); + case PTPBASE_CLOCK_PORT_DS_PEER_DELAY_REQ_INTERVAL: + /* TODO: is it really logMinPdelayReqInterval? */ + return SNMP_INTEGER(snmpPtpClock->portDS.logMinPdelayReqInterval); + case PTPBASE_CLOCK_PORT_DS_DELAY_MECH: + return SNMP_INTEGER(snmpPtpClock->portDS.delayMechanism); + case PTPBASE_CLOCK_PORT_DS_PEER_MEAN_PATH_DELAY: + return SNMP_TIMEINTERNAL(snmpPtpClock->portDS.peerMeanPathDelay); + case PTPBASE_CLOCK_PORT_DS_GRANT_DURATION: + if(snmpRtOpts->unicastNegotiation && snmpPtpClock->parentGrants) { + return SNMP_UNSIGNED(snmpPtpClock->parentGrants->grantData[SYNC_INDEXED].duration); + } + return SNMP_UNSIGNED(0); + case PTPBASE_CLOCK_PORT_DS_PTP_VERSION: + return SNMP_INTEGER(snmpPtpClock->portDS.versionNumber); + case PTPBASE_CLOCK_PORT_DS_PEER_MEAN_PATH_DELAY_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->portDS.peerMeanPathDelay)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_CLOCK_PORT_DS_LAST_MISMATCHED_DOMAIN: + return SNMP_INTEGER(snmpPtpClock->portDS.lastMismatchedDomain); + + /* ptpbaseClockPortRunningTable */ + case PTPBASE_CLOCK_PORT_RUNNING_STATE: + return SNMP_INTEGER(snmpPtpClock->portDS.portState); + case PTPBASE_CLOCK_PORT_RUNNING_ROLE: + return SNMP_INTEGER((snmpPtpClock->portDS.portState == PTP_MASTER)? + SNMP_PTP_PORT_MASTER:SNMP_PTP_PORT_SLAVE); + case PTPBASE_CLOCK_PORT_RUNNING_INTERFACE_INDEX: + return SNMP_INTEGER(snmpPtpClock->netPath.interfaceInfo.ifIndex); + case PTPBASE_CLOCK_PORT_RUNNING_IPVERSION: + /* IPv4 only */ + return SNMP_INTEGER(4); + case PTPBASE_CLOCK_PORT_RUNNING_ENCAPSULATION_TYPE: + /* None. Moreover, the format is not really described in the MIB... */ + return SNMP_INTEGER(0); + case PTPBASE_CLOCK_PORT_RUNNING_TX_MODE: + case PTPBASE_CLOCK_PORT_RUNNING_RX_MODE: + if (snmpRtOpts->ipMode == IPMODE_UNICAST) + return SNMP_INTEGER(SNMP_PTP_TX_UNICAST); + if (snmpRtOpts->ipMode == IPMODE_HYBRID) + return SNMP_INTEGER(SNMP_PTP_TX_MULTICAST_MIX); + return SNMP_INTEGER(SNMP_PTP_TX_MULTICAST); + case PTPBASE_CLOCK_PORT_RUNNING_PACKETS_RECEIVED: + + return SNMP_COUNTER64(snmpPtpClock->netPath.receivedPacketsTotal); + case PTPBASE_CLOCK_PORT_RUNNING_PACKETS_SENT: + return SNMP_COUNTER64(snmpPtpClock->netPath.sentPacketsTotal); + } + + + return NULL; +} + +/* clear counter sets based on oid. WARNING: USES MAGIC NUMBERS... */ +static int +snmpWriteClearCounters (int action, u_char *var_val, u_char var_val_type, size_t var_val_len, + u_char *statP, oid *name, size_t name_len) { + + /* table: 6 oids from end (index fields, entry, field) */ + oid myOid1 = name[name_len - 1 - 6]; + /* field: 4 oids from end (index fields) */ + oid myOid2 = name[name_len - 1 - 4]; + + if(var_val_type != ASN_INTEGER) { + return SNMP_ERR_WRONGTYPE; + } + + if(var_val_len > sizeof(long)) { + return SNMP_ERR_WRONGLENGTH; + } + + if(action==COMMIT) { + + long *val = (long*) var_val; + + if (*val == TRUTHVALUE_TRUE) { + + switch (myOid1) { + + case 12: /* message counters */ + /* all counters */ + if(myOid2 == 5) { + memset(&snmpPtpClock->counters, 0, sizeof(PtpdCounters)); + return SNMP_ERR_NOERROR; + } + /* message counters */ + if(myOid2 == 6) { + snmpPtpClock->counters.announceMessagesSent = 0; + snmpPtpClock->counters.announceMessagesReceived = 0; + snmpPtpClock->counters.syncMessagesSent = 0; + snmpPtpClock->counters.syncMessagesReceived = 0; + snmpPtpClock->counters.followUpMessagesSent = 0; + snmpPtpClock->counters.followUpMessagesReceived = 0; + snmpPtpClock->counters.delayReqMessagesSent = 0; + snmpPtpClock->counters.delayReqMessagesReceived = 0; + snmpPtpClock->counters.delayRespMessagesSent = 0; + snmpPtpClock->counters.delayRespMessagesReceived = 0; + snmpPtpClock->counters.pdelayReqMessagesSent = 0; + snmpPtpClock->counters.pdelayReqMessagesReceived = 0; + snmpPtpClock->counters.pdelayRespMessagesSent = 0; + snmpPtpClock->counters.pdelayRespMessagesReceived = 0; + snmpPtpClock->counters.pdelayRespFollowUpMessagesSent = 0; + snmpPtpClock->counters.pdelayRespFollowUpMessagesReceived = 0; + snmpPtpClock->counters.signalingMessagesSent = 0; + snmpPtpClock->counters.signalingMessagesReceived = 0; + snmpPtpClock->counters.managementMessagesSent = 0; + snmpPtpClock->counters.managementMessagesReceived = 0; + snmpPtpClock->counters.discardedMessages = 0; + snmpPtpClock->counters.unknownMessages = 0; + return SNMP_ERR_NOERROR; + } + break; + case 13: /* protocol counters */ + /* clear counters */ + if(myOid2 == 5) { + snmpPtpClock->counters.foreignAdded = 0; + /* snmpPtpClock->counters.foreignCount = 0; */ /* we don't clear this */ + snmpPtpClock->counters.foreignRemoved = 0; + snmpPtpClock->counters.foreignOverflows = 0; + snmpPtpClock->counters.stateTransitions = 0; + snmpPtpClock->counters.bestMasterChanges = 0; + snmpPtpClock->counters.announceTimeouts = 0; + return SNMP_ERR_NOERROR; + } + break; + case 14: /* error counters */ + /* clear counters */ + if(myOid2 == 5) { + snmpPtpClock->counters.messageRecvErrors = 0; + snmpPtpClock->counters.messageSendErrors = 0; + snmpPtpClock->counters.messageFormatErrors = 0; + snmpPtpClock->counters.protocolErrors = 0; + snmpPtpClock->counters.versionMismatchErrors = 0; + snmpPtpClock->counters.domainMismatchErrors = 0; + snmpPtpClock->counters.sequenceMismatchErrors = 0; + snmpPtpClock->counters.delayMechanismMismatchErrors = 0; + return SNMP_ERR_NOERROR; + } + break; + case 15: /* unicast negotiation counters */ + /* clear counters */ + if(myOid2 == 5) { + snmpPtpClock->counters.unicastGrantsRequested = 0; + snmpPtpClock->counters.unicastGrantsGranted = 0; + snmpPtpClock->counters.unicastGrantsDenied = 0; + snmpPtpClock->counters.unicastGrantsCancelSent = 0; + snmpPtpClock->counters.unicastGrantsCancelReceived = 0; + snmpPtpClock->counters.unicastGrantsCancelAckSent = 0; + snmpPtpClock->counters.unicastGrantsCancelAckReceived = 0; + return SNMP_ERR_NOERROR; + } + break; + case 17: /* security counters */ + /* clear counters */ + if(myOid2 == 5) { + snmpPtpClock->counters.aclTimingMessagesDiscarded = 0; + snmpPtpClock->counters.aclManagementMessagesDiscarded = 0; + return SNMP_ERR_NOERROR; + } + break; + case 21: /* ptpd counters */ + /* clear counters */ + if(myOid2 == 5) { + snmpPtpClock->counters.consecutiveSequenceErrors = 0; + snmpPtpClock->counters.ignoredAnnounce = 0; +#ifdef PTPD_STATISTICS + snmpPtpClock->counters.delayMSOutliersFound = 0; + snmpPtpClock->counters.delaySMOutliersFound = 0; +#endif + snmpPtpClock->counters.maxDelayDrops = 0; + return SNMP_ERR_NOERROR; + } + break; + default: + return SNMP_ERR_WRONGVALUE; + } + return SNMP_ERR_WRONGVALUE; + } + + return SNMP_ERR_WRONGVALUE; + + } + + return SNMP_ERR_NOERROR; + +} + +/** + * Handle ptpbasePtpPortMessageCounters + */ +static u_char* +snmpPtpPortMessageCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PORT_MESSAGE_COUNTERS_CLEAR: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PORT_MESSAGE_COUNTERS_CLEAR_ALL: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PORT_MESSAGE_COUNTERS_TOTAL_SENT: + return SNMP_INTEGER(snmpPtpClock->netPath.sentPacketsTotal); + case PTPBASE_PORT_MESSAGE_COUNTERS_TOTAL_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->netPath.receivedPacketsTotal); + case PTPBASE_PORT_MESSAGE_COUNTERS_ANNOUNCE_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.announceMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_ANNOUNCE_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.announceMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_SYNC_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.syncMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_SYNC_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.syncMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_FOLLOWUP_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.followUpMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_FOLLOWUP_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.followUpMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_DELAYREQ_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.delayReqMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_DELAYREQ_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.delayReqMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_DELAYRESP_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.delayRespMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_DELAYRESP_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.delayRespMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYREQ_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.pdelayReqMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYREQ_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.pdelayReqMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.pdelayRespMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.pdelayRespMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_FOLLOWUP_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.pdelayRespFollowUpMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_FOLLOWUP_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.pdelayRespFollowUpMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_SIGNALING_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.signalingMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_SIGNALING_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.signalingMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_MANAGEMENT_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.managementMessagesSent); + case PTPBASE_PORT_MESSAGE_COUNTERS_MANAGEMENT_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.managementMessagesReceived); + case PTPBASE_PORT_MESSAGE_COUNTERS_DISCARDED_MESSAGES: + return SNMP_INTEGER(snmpPtpClock->counters.discardedMessages); + case PTPBASE_PORT_MESSAGE_COUNTERS_UNKNOWN_MESSAGES: + return SNMP_INTEGER(snmpPtpClock->counters.unknownMessages); + + } + + return NULL; +} + + +/** + * Handle ptpbasePtpPortProtocolCounters + */ +static u_char* +snmpPtpPortProtocolCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PORT_PROTOCOL_COUNTERS_CLEAR: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_ADDED: + return SNMP_INTEGER(snmpPtpClock->counters.foreignAdded); + case PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_COUNT: + return SNMP_INTEGER(snmpPtpClock->counters.foreignCount); + case PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_REMOVED: + return SNMP_INTEGER(snmpPtpClock->counters.foreignRemoved); + case PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_OVERFLOWS: + return SNMP_INTEGER(snmpPtpClock->counters.foreignOverflows); + case PTPBASE_PORT_PROTOCOL_COUNTERS_STATE_TRANSITIONS: + return SNMP_INTEGER(snmpPtpClock->counters.stateTransitions); + case PTPBASE_PORT_PROTOCOL_COUNTERS_BEST_MASTER_CHANGES: + return SNMP_INTEGER(snmpPtpClock->counters.bestMasterChanges); + case PTPBASE_PORT_PROTOCOL_COUNTERS_ANNOUNCE_TIMEOUTS: + return SNMP_INTEGER(snmpPtpClock->counters.announceTimeouts); + } + + return NULL; +} + +/** + * Handle ptpbasePtpPortErrorCounters + */ +static u_char* +snmpPtpPortErrorCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PORT_ERROR_COUNTERS_CLEAR: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_RECV: + return SNMP_INTEGER(snmpPtpClock->counters.messageRecvErrors); + case PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_SEND: + return SNMP_INTEGER(snmpPtpClock->counters.messageSendErrors); + case PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_FORMAT: + return SNMP_INTEGER(snmpPtpClock->counters.messageFormatErrors); + case PTPBASE_PORT_ERROR_COUNTERS_PROTOCOL: + return SNMP_INTEGER(snmpPtpClock->counters.protocolErrors); + case PTPBASE_PORT_ERROR_COUNTERS_VERSION_MISMATCH: + return SNMP_INTEGER(snmpPtpClock->counters.versionMismatchErrors); + case PTPBASE_PORT_ERROR_COUNTERS_DOMAIN_MISMATCH: + return SNMP_INTEGER(snmpPtpClock->counters.domainMismatchErrors); + case PTPBASE_PORT_ERROR_COUNTERS_SEQUENCE_MISMATCH: + return SNMP_INTEGER(snmpPtpClock->counters.sequenceMismatchErrors); + case PTPBASE_PORT_ERROR_COUNTERS_DELAYMECH_MISMATCH: + return SNMP_INTEGER(snmpPtpClock->counters.delayMechanismMismatchErrors); + } + + return NULL; +} + +/** + * Handle ptpbasePtpPortUnicastNegotiationCounters + */ +static u_char* +snmpPtpPortUnicastNegotiationCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_CLEAR: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_REQUESTED: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsRequested); + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_GRANTED: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsGranted); + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_DENIED: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsDenied); + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsCancelSent); + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsCancelReceived); + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_ACK_SENT: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsCancelAckSent); + case PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_ACK_RECEIVED: + return SNMP_INTEGER(snmpPtpClock->counters.unicastGrantsCancelAckReceived); + } + + return NULL; +} + +/** + * Handle ptpbasePtpPortPerformanceCounters + */ +static u_char* +snmpPtpPortPerformanceCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PORT_PERFORMANCE_COUNTERS_MESSAGE_SEND_RATE: + return SNMP_INTEGER(snmpPtpClock->counters.messageSendRate); + case PTPBASE_PORT_PERFORMANCE_COUNTERS_MESSAGE_RECEIVE_RATE: + return SNMP_INTEGER(snmpPtpClock->counters.messageReceiveRate); + } + + return NULL; +} + + +/** + * Handle ptpbasePtpPortSecurityCounters + */ +static u_char* +snmpPtpPortSecurityCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PORT_SECURITY_COUNTERS_CLEAR: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PORT_SECURITY_COUNTERS_TIMING_ACL_DISCARDED: + return SNMP_INTEGER(snmpPtpClock->counters.aclTimingMessagesDiscarded); + case PTPBASE_PORT_SECURITY_COUNTERS_MANAGEMENT_ACL_DISCARDED: + return SNMP_INTEGER(snmpPtpClock->counters.aclManagementMessagesDiscarded); + } + + return NULL; +} + +/** + * Handle slaveOfmStatisticsTable + */ +static u_char* +snmpSlaveOfmStatsTable(SNMP_SIGNATURE) { + oid index[3]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + memset(tmpStr, 0, sizeof(tmpStr)); + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + SNMP_ADD_INDEX(index, 3, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_SLAVE_OFM_STATS_CURRENT_VALUE: + return SNMP_TIMEINTERNAL(snmpPtpClock->currentDS.offsetFromMaster); + case PTPBASE_SLAVE_OFM_STATS_CURRENT_VALUE_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->currentDS.offsetFromMaster)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); +#ifdef PTPD_STATISTICS + case PTPBASE_SLAVE_OFM_STATS_PERIOD_SECONDS: + return SNMP_INTEGER(snmpRtOpts->statsUpdateInterval); + case PTPBASE_SLAVE_OFM_STATS_VALID: + return SNMP_BOOLEAN(snmpPtpClock->slaveStats.statsCalculated); + case PTPBASE_SLAVE_OFM_STATS_MIN: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.ofmMinFinal)); + case PTPBASE_SLAVE_OFM_STATS_MAX: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.ofmMaxFinal)); + case PTPBASE_SLAVE_OFM_STATS_MEAN: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.ofmMean)); + case PTPBASE_SLAVE_OFM_STATS_STDDEV: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.ofmStdDev)); + case PTPBASE_SLAVE_OFM_STATS_MEDIAN: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.ofmMedian)); + case PTPBASE_SLAVE_OFM_STATS_MIN_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.ofmMinFinal); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_OFM_STATS_MAX_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.ofmMaxFinal); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_OFM_STATS_MEAN_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.ofmMean); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_OFM_STATS_STDDEV_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.ofmStdDev); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_OFM_STATS_MEDIAN_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.ofmMedian); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); +#endif + } + + return NULL; +} + +/** + * Handle slaveMpdStatisticsTable + */ +static u_char* +snmpSlaveMpdStatsTable(SNMP_SIGNATURE) { + oid index[3]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + memset(tmpStr, 0, sizeof(tmpStr)); + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + SNMP_ADD_INDEX(index, 3, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_SLAVE_MPD_STATS_CURRENT_VALUE: + return SNMP_TIMEINTERNAL(snmpPtpClock->currentDS.meanPathDelay); + case PTPBASE_SLAVE_MPD_STATS_CURRENT_VALUE_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->currentDS.meanPathDelay)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); +#ifdef PTPD_STATISTICS + case PTPBASE_SLAVE_MPD_STATS_PERIOD_SECONDS: + return SNMP_INTEGER(snmpRtOpts->statsUpdateInterval); + case PTPBASE_SLAVE_MPD_STATS_VALID: + return SNMP_BOOLEAN(snmpPtpClock->slaveStats.statsCalculated); + case PTPBASE_SLAVE_MPD_STATS_MIN: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.mpdMinFinal)); + case PTPBASE_SLAVE_MPD_STATS_MAX: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.mpdMaxFinal)); + case PTPBASE_SLAVE_MPD_STATS_MEAN: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.mpdMean)); + case PTPBASE_SLAVE_MPD_STATS_STDDEV: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.mpdStdDev)); + case PTPBASE_SLAVE_MPD_STATS_MEDIAN: + return SNMP_TIMEINTERNAL(doubleToTimeInternal(snmpPtpClock->slaveStats.mpdMedian)); + case PTPBASE_SLAVE_MPD_STATS_MIN_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.mpdMinFinal); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_MPD_STATS_MAX_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.mpdMaxFinal); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_MPD_STATS_MEAN_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.mpdMean); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_MPD_STATS_STDDEV_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.mpdStdDev); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_SLAVE_MPD_STATS_MEDIAN_STRING: + snprintf(tmpStr, 64, "%.09f", snmpPtpClock->slaveStats.mpdMedian); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); +#endif + } + + return NULL; +} + +/** + * Handle slaveFreqAdjStatisticsTable + */ +static u_char* +snmpSlaveFreqAdjStatsTable(SNMP_SIGNATURE) { + oid index[3]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + memset(tmpStr, 0, sizeof(tmpStr)); + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + SNMP_ADD_INDEX(index, 3, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_SLAVE_FREQADJ_STATS_CURRENT_VALUE: + return SNMP_INTEGER(snmpPtpClock->servo.observedDrift); +#ifdef PTPD_STATISTICS + case PTPBASE_SLAVE_FREQADJ_STATS_PERIOD_SECONDS: + return SNMP_INTEGER(snmpRtOpts->statsUpdateInterval); + case PTPBASE_SLAVE_FREQADJ_STATS_VALID: + return SNMP_BOOLEAN(snmpPtpClock->servo.statsCalculated); + case PTPBASE_SLAVE_FREQADJ_STATS_MIN: + return SNMP_INTEGER(snmpPtpClock->servo.driftMinFinal); + case PTPBASE_SLAVE_FREQADJ_STATS_MAX: + return SNMP_INTEGER(snmpPtpClock->servo.driftMaxFinal); + case PTPBASE_SLAVE_FREQADJ_STATS_MEAN: + return SNMP_INTEGER(snmpPtpClock->servo.driftMean); + case PTPBASE_SLAVE_FREQADJ_STATS_STDDEV: + return SNMP_INTEGER(snmpPtpClock->servo.driftStdDev); + case PTPBASE_SLAVE_FREQADJ_STATS_MEDIAN: + return SNMP_INTEGER(snmpPtpClock->servo.driftMedian); +#endif + } + + return NULL; +} + + + +/** + * Handle ptpbasePtpdSpecificCounters + */ +static u_char* +snmpPtpdSpecificCountersTable(SNMP_SIGNATURE) { + oid index[4]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + index[3] = snmpPtpClock->portDS.portIdentity.portNumber; + SNMP_ADD_INDEX(index, 4, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + + switch (vp->magic) { + case PTPBASE_PTPD_SPECIFIC_COUNTERS_CLEAR: + *write_method = snmpWriteClearCounters; + return SNMP_FALSE; + case PTPBASE_PTPD_SPECIFIC_COUNTERS_IGNORED_ANNOUNCE: + return SNMP_INTEGER(snmpPtpClock->counters.ignoredAnnounce); + case PTPBASE_PTPD_SPECIFIC_COUNTERS_CONSECUTIVE_SEQUENCE_ERRORS: + return SNMP_INTEGER(snmpPtpClock->counters.consecutiveSequenceErrors); +#ifdef PTPD_STATISTICS + case PTPBASE_PTPD_SPECIFIC_COUNTERS_DELAYMS_OUTLIERS_FOUND: + return SNMP_INTEGER(snmpPtpClock->counters.delayMSOutliersFound); + case PTPBASE_PTPD_SPECIFIC_COUNTERS_DELAYSM_OUTLIERS_FOUND: + return SNMP_INTEGER(snmpPtpClock->counters.delaySMOutliersFound); +#endif + case PTPBASE_PTPD_SPECIFIC_COUNTERS_MAX_DELAY_DROPS: + return SNMP_INTEGER(snmpPtpClock->counters.maxDelayDrops); + } + + return NULL; +} + +/** + * Handle ptpBasePtpdSpecificData + */ +static u_char* +snmpPtpdSpecificDataTable(SNMP_SIGNATURE) { + oid index[3]; + SNMP_LOCAL_VARIABLES; + SNMP_INDEXED_TABLE; + + memset(tmpStr, 0, sizeof(tmpStr)); + + /* We only have one valid index */ + index[0] = snmpPtpClock->defaultDS.domainNumber; + index[1] = SNMP_PTP_ORDINARY_CLOCK; + index[2] = SNMP_PTP_CLOCK_INSTANCE; + SNMP_ADD_INDEX(index, 3, snmpPtpClock); + + if (!SNMP_BEST_MATCH) return NULL; + +#ifdef PTPD_STATISTICS + switch (vp->magic) { + case PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYMS: + return SNMP_TIMEINTERNAL(snmpPtpClock->rawDelayMS); + case PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYMS_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->rawDelayMS)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + case PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYSM: + return SNMP_TIMEINTERNAL(snmpPtpClock->rawDelaySM); + case PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYSM_STRING: + snprintf(tmpStr, 64, "%.09f", timeInternalToDouble(&snmpPtpClock->rawDelaySM)); + return SNMP_OCTETSTR(&tmpStr, strlen(tmpStr)); + } +#endif + + return NULL; +} + + + +/** + * MIB definition + */ +static struct variable7 snmpVariables[] = { + /* ptpbaseSystemTable */ + { PTPBASE_DOMAIN_CLOCK_PORTS_TOTAL, ASN_GAUGE, HANDLER_CAN_RONLY, + snmpSystemTable, 5, {1, 1, 1, 1, 3}}, + /* ptpbaseSystemDomainTable */ + { PTPBASE_SYSTEM_DOMAIN_TOTALS, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpSystemDomainTable, 5, {1, 1, 2, 1, 2}}, + /* Scalars */ + { PTPBASE_SYSTEM_PROFILE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpScalars, 3, {1, 1, 3}}, + /* ptpbaseClockCurrentDSTable */ + { PTPBASE_CLOCK_CURRENT_DS_STEPS_REMOVED, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 1, 1, 4}}, + { PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 1, 1, 5}}, + { PTPBASE_CLOCK_CURRENT_DS_MEAN_PATH_DELAY, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 1, 1, 6}}, + /* PTPd enhancements */ + { PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 1, 1, 7}}, + { PTPBASE_CLOCK_CURRENT_DS_MEAN_PATH_DELAY_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 1, 1, 8}}, + { PTPBASE_CLOCK_CURRENT_DS_OFFSET_FROM_MASTER_THRESHOLD, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 1, 1, 9}}, + + /* ptpbaseClockParentDSTable */ + { PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ID, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 4}}, + { PTPBASE_CLOCK_PARENT_DS_PARENT_STATS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 5}}, + { PTPBASE_CLOCK_PARENT_DS_OFFSET, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 6}}, + { PTPBASE_CLOCK_PARENT_DS_CLOCK_PH_CH_RATE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 7}}, + { PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_IDENTITY, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 8}}, + { PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_PRIO1, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 9}}, + { PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_PRIO2, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 10}}, + { PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_CLASS, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 11}}, + { PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_ACCURACY, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 12}}, + { PTPBASE_CLOCK_PARENT_DS_GM_CLOCK_QUALITY_OFFSET, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 13}}, + + /* PTPd enhancements */ + { PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ADDRESS_TYPE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 14}}, + { PTPBASE_CLOCK_PARENT_DS_PARENT_PORT_ADDRESS, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 2, 1, 15}}, + + /* ptpbaseClockDefaultDSTable */ + { PTPBASE_CLOCK_DEFAULT_DS_TWO_STEP_FLAG, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 4}}, + { PTPBASE_CLOCK_DEFAULT_DS_CLOCK_IDENTITY, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 5}}, + { PTPBASE_CLOCK_DEFAULT_DS_PRIO1, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 6}}, + { PTPBASE_CLOCK_DEFAULT_DS_PRIO2, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 7}}, + { PTPBASE_CLOCK_DEFAULT_DS_SLAVE_ONLY, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 8}}, + { PTPBASE_CLOCK_DEFAULT_DS_QUALITY_CLASS, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 9}}, + { PTPBASE_CLOCK_DEFAULT_DS_QUALITY_ACCURACY, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 10}}, + { PTPBASE_CLOCK_DEFAULT_DS_QUALITY_OFFSET, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 11}}, + /* PTPd addition - this is part of the index, but if you use snmpgetnext, you guess the indexes */ + { PTPBASE_CLOCK_DEFAULT_DS_DOMAIN_NUMBER, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 3, 1, 12}}, + /* ptpbaseClockRunningTable: no data available for this table? */ + /* ptpbaseClockTimePropertiesDSTable */ + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_CURRENT_UTC_OFFSET_VALID, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 4}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_CURRENT_UTC_OFFSET, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 5}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_LEAP59, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 6}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_LEAP61, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 7}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_TIME_TRACEABLE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 8}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_FREQ_TRACEABLE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 9}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_PTP_TIMESCALE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 10}}, + { PTPBASE_CLOCK_TIME_PROPERTIES_DS_SOURCE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockDSTable, 5, {1, 2, 5, 1, 11}}, + /* ptpbaseClockPortTable */ + { PTPBASE_CLOCK_PORT_NAME, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 7, 1, 5}}, + { PTPBASE_CLOCK_PORT_ROLE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 7, 1, 6}}, + { PTPBASE_CLOCK_PORT_SYNC_ONE_STEP, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 7, 1, 7}}, + { PTPBASE_CLOCK_PORT_CURRENT_PEER_ADDRESS_TYPE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 7, 1, 8}}, + { PTPBASE_CLOCK_PORT_CURRENT_PEER_ADDRESS, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 7, 1, 9}}, + { PTPBASE_CLOCK_PORT_NUM_ASSOCIATED_PORTS, ASN_GAUGE, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 7, 1, 10}}, + /* ptpbaseClockPortDSTable */ + { PTPBASE_CLOCK_PORT_DS_PORT_NAME, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 5}}, + { PTPBASE_CLOCK_PORT_DS_PORT_IDENTITY, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 6}}, + { PTPBASE_CLOCK_PORT_DS_ANNOUNCEMENT_INTERVAL, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 7}}, + { PTPBASE_CLOCK_PORT_DS_ANNOUNCE_RCT_TIMEOUT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 8}}, + { PTPBASE_CLOCK_PORT_DS_SYNC_INTERVAL, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 9}}, + { PTPBASE_CLOCK_PORT_DS_MIN_DELAY_REQ_INTERVAL, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 10}}, + { PTPBASE_CLOCK_PORT_DS_PEER_DELAY_REQ_INTERVAL, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 11}}, + { PTPBASE_CLOCK_PORT_DS_DELAY_MECH, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 12}}, + { PTPBASE_CLOCK_PORT_DS_PEER_MEAN_PATH_DELAY, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 13}}, + { PTPBASE_CLOCK_PORT_DS_GRANT_DURATION, ASN_UNSIGNED, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 14}}, + { PTPBASE_CLOCK_PORT_DS_PTP_VERSION, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 15}}, + { PTPBASE_CLOCK_PORT_DS_PEER_MEAN_PATH_DELAY_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 16}}, + { PTPBASE_CLOCK_PORT_DS_LAST_MISMATCHED_DOMAIN, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 8, 1, 17}}, + /* ptpbaseClockPortRunningTable */ + { PTPBASE_CLOCK_PORT_RUNNING_NAME, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 5}}, + { PTPBASE_CLOCK_PORT_RUNNING_STATE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 6}}, + { PTPBASE_CLOCK_PORT_RUNNING_ROLE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 7}}, + { PTPBASE_CLOCK_PORT_RUNNING_INTERFACE_INDEX, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 8}}, + { PTPBASE_CLOCK_PORT_RUNNING_IPVERSION, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 9}}, + { PTPBASE_CLOCK_PORT_RUNNING_ENCAPSULATION_TYPE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 10}}, + { PTPBASE_CLOCK_PORT_RUNNING_TX_MODE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 11}}, + { PTPBASE_CLOCK_PORT_RUNNING_RX_MODE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 12}}, + { PTPBASE_CLOCK_PORT_RUNNING_PACKETS_RECEIVED, ASN_COUNTER64, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 13}}, + { PTPBASE_CLOCK_PORT_RUNNING_PACKETS_SENT, ASN_COUNTER64, HANDLER_CAN_RONLY, + snmpClockPortTable, 5, {1, 2, 9, 1, 14}}, + /* ptpbasePtpPortMessageCounters */ + { PTPBASE_PORT_MESSAGE_COUNTERS_CLEAR_ALL, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 5}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_CLEAR, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 6}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_TOTAL_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 7}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_TOTAL_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 8}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_ANNOUNCE_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 9}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_ANNOUNCE_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 10}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_SYNC_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 11}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_SYNC_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 12}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_FOLLOWUP_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 13}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_FOLLOWUP_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 14}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_DELAYREQ_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 15}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_DELAYREQ_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 16}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_DELAYRESP_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 17}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_DELAYRESP_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 18}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYREQ_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 19}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYREQ_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 20}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 21}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 22}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_FOLLOWUP_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 23}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_PDELAYRESP_FOLLOWUP_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 24}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_SIGNALING_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 25}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_SIGNALING_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 26}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_MANAGEMENT_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 27}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_MANAGEMENT_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 28}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_DISCARDED_MESSAGES, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 29}}, + { PTPBASE_PORT_MESSAGE_COUNTERS_UNKNOWN_MESSAGES, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortMessageCountersTable, 5, {1, 2, 12, 1, 30}}, + /* ptpbasePtpPortProtocolCounters */ + { PTPBASE_PORT_PROTOCOL_COUNTERS_CLEAR, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 5}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_ADDED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 6}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_COUNT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 7}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_REMOVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 8}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_FOREIGN_OVERFLOWS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 9}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_STATE_TRANSITIONS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 10}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_BEST_MASTER_CHANGES, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 11}}, + { PTPBASE_PORT_PROTOCOL_COUNTERS_ANNOUNCE_TIMEOUTS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortProtocolCountersTable, 5, {1, 2, 13, 1, 12}}, + /* ptpbasePtpPortErrorCounters */ + { PTPBASE_PORT_ERROR_COUNTERS_CLEAR, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 5}}, + { PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_RECV, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 6}}, + { PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_SEND, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 7}}, + { PTPBASE_PORT_ERROR_COUNTERS_MESSAGE_FORMAT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 8}}, + { PTPBASE_PORT_ERROR_COUNTERS_PROTOCOL, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 9}}, + { PTPBASE_PORT_ERROR_COUNTERS_VERSION_MISMATCH, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 10}}, + { PTPBASE_PORT_ERROR_COUNTERS_DOMAIN_MISMATCH, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 11}}, + { PTPBASE_PORT_ERROR_COUNTERS_SEQUENCE_MISMATCH, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 12}}, + { PTPBASE_PORT_ERROR_COUNTERS_DELAYMECH_MISMATCH, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortErrorCountersTable, 5, {1, 2, 14, 1, 13}}, + /* ptpbasePtpPortUnicastNegotiationCounters */ + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_CLEAR, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 5}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_REQUESTED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 6}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_GRANTED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 7}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_DENIED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 8}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 9}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 10}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_ACK_SENT, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 11}}, + { PTPBASE_PORT_UNICAST_NEGOTIATION_COUNTERS_GRANTS_CANCEL_ACK_RECEIVED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortUnicastNegotiationCountersTable, 5, {1, 2, 15, 1, 12}}, + /* ptpbasePtpPortPerformanceCounters - no clear here */ + { PTPBASE_PORT_PERFORMANCE_COUNTERS_MESSAGE_SEND_RATE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortPerformanceCountersTable, 5, {1, 2, 16, 1, 5}}, + { PTPBASE_PORT_PERFORMANCE_COUNTERS_MESSAGE_RECEIVE_RATE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortPerformanceCountersTable, 5, {1, 2, 16, 1, 6}}, + /* ptpbasePtpPortSecurityCounters */ + { PTPBASE_PORT_SECURITY_COUNTERS_CLEAR, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpPortSecurityCountersTable, 5, {1, 2, 17, 1, 5}}, + { PTPBASE_PORT_SECURITY_COUNTERS_TIMING_ACL_DISCARDED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortSecurityCountersTable, 5, {1, 2, 17, 1, 6}}, + { PTPBASE_PORT_SECURITY_COUNTERS_MANAGEMENT_ACL_DISCARDED, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpPortSecurityCountersTable, 5, {1, 2, 17, 1, 7}}, + /* ptpBaseSlaveOfmStatistics */ + { PTPBASE_SLAVE_OFM_STATS_CURRENT_VALUE, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 4}}, + { PTPBASE_SLAVE_OFM_STATS_CURRENT_VALUE_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 5}}, + { PTPBASE_SLAVE_OFM_STATS_PERIOD_SECONDS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 6}}, + { PTPBASE_SLAVE_OFM_STATS_VALID, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 7}}, + { PTPBASE_SLAVE_OFM_STATS_MIN, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 8}}, + { PTPBASE_SLAVE_OFM_STATS_MAX, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 9}}, + { PTPBASE_SLAVE_OFM_STATS_MEAN, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 10}}, + { PTPBASE_SLAVE_OFM_STATS_STDDEV, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 11}}, + { PTPBASE_SLAVE_OFM_STATS_MEDIAN, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 12}}, + { PTPBASE_SLAVE_OFM_STATS_MIN_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 13}}, + { PTPBASE_SLAVE_OFM_STATS_MAX_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 14}}, + { PTPBASE_SLAVE_OFM_STATS_MEAN_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 15}}, + { PTPBASE_SLAVE_OFM_STATS_STDDEV_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 16}}, + { PTPBASE_SLAVE_OFM_STATS_MEDIAN_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveOfmStatsTable, 5, {1, 2, 18, 1, 17}}, + /* ptpBaseSlaveMpdStatistics */ + { PTPBASE_SLAVE_MPD_STATS_CURRENT_VALUE, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 4}}, + { PTPBASE_SLAVE_MPD_STATS_CURRENT_VALUE_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 5}}, + { PTPBASE_SLAVE_MPD_STATS_PERIOD_SECONDS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 6}}, + { PTPBASE_SLAVE_MPD_STATS_VALID, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 7}}, + { PTPBASE_SLAVE_MPD_STATS_MIN, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 8}}, + { PTPBASE_SLAVE_MPD_STATS_MAX, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 9}}, + { PTPBASE_SLAVE_MPD_STATS_MEAN, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 10}}, + { PTPBASE_SLAVE_MPD_STATS_STDDEV, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 11}}, + { PTPBASE_SLAVE_MPD_STATS_MEDIAN, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 12}}, + { PTPBASE_SLAVE_MPD_STATS_MIN_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 13}}, + { PTPBASE_SLAVE_MPD_STATS_MAX_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 14}}, + { PTPBASE_SLAVE_MPD_STATS_MEAN_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 15}}, + { PTPBASE_SLAVE_MPD_STATS_STDDEV_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 16}}, + { PTPBASE_SLAVE_MPD_STATS_MEDIAN_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpSlaveMpdStatsTable, 5, {1, 2, 19, 1, 17}}, + /* ptpBaseSlaveFreqAdjStatistics */ + { PTPBASE_SLAVE_FREQADJ_STATS_CURRENT_VALUE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 4}}, + { PTPBASE_SLAVE_FREQADJ_STATS_PERIOD_SECONDS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 5}}, + { PTPBASE_SLAVE_FREQADJ_STATS_VALID, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 6}}, + { PTPBASE_SLAVE_FREQADJ_STATS_MIN, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 7}}, + { PTPBASE_SLAVE_FREQADJ_STATS_MAX, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 8}}, + { PTPBASE_SLAVE_FREQADJ_STATS_MEAN, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 9}}, + { PTPBASE_SLAVE_FREQADJ_STATS_STDDEV, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 10}}, + { PTPBASE_SLAVE_FREQADJ_STATS_MEDIAN, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpSlaveFreqAdjStatsTable, 5, {1, 2, 20, 1, 11}}, + /* ptpbasePtpdSpecificCounters */ + { PTPBASE_PTPD_SPECIFIC_COUNTERS_CLEAR, ASN_INTEGER, HANDLER_CAN_RWRITE, + snmpPtpdSpecificCountersTable, 5, {1, 2, 21, 1, 5}}, + { PTPBASE_PTPD_SPECIFIC_COUNTERS_IGNORED_ANNOUNCE, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpdSpecificCountersTable, 5, {1, 2, 21, 1, 6}}, + { PTPBASE_PTPD_SPECIFIC_COUNTERS_CONSECUTIVE_SEQUENCE_ERRORS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpdSpecificCountersTable, 5, {1, 2, 21, 1, 7}}, + { PTPBASE_PTPD_SPECIFIC_COUNTERS_DELAYMS_OUTLIERS_FOUND, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpdSpecificCountersTable, 5, {1, 2, 21, 1, 8}}, + { PTPBASE_PTPD_SPECIFIC_COUNTERS_DELAYSM_OUTLIERS_FOUND, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpdSpecificCountersTable, 5, {1, 2, 21, 1, 9}}, + { PTPBASE_PTPD_SPECIFIC_COUNTERS_MAX_DELAY_DROPS, ASN_INTEGER, HANDLER_CAN_RONLY, + snmpPtpdSpecificCountersTable, 5, {1, 2, 21, 1, 10}}, + /* ptpBasePtpdSpecificData*/ + { PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYMS, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpPtpdSpecificDataTable, 5, {1, 2, 22, 1, 4}}, + { PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYMS_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpPtpdSpecificDataTable, 5, {1, 2, 22, 1, 5}}, + { PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYSM, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpPtpdSpecificDataTable, 5, {1, 2, 22, 1, 6}}, + { PTPBASE_PTPD_SPECIFIC_DATA_RAW_DELAYSM_STRING, ASN_OCTET_STR, HANDLER_CAN_RONLY, + snmpPtpdSpecificDataTable, 5, {1, 2, 22, 1, 7}} +}; + +/** + * Log messages from NetSNMP subsystem. + */ +static int +snmpLogCallback(int major, int minor, + void *serverarg, void *clientarg) +{ + struct snmp_log_message *slm = (struct snmp_log_message *)serverarg; + char *msg = strdup (slm->msg); + if (msg) msg[strlen(msg)-1] = '\0'; + + switch (slm->priority) + { + case LOG_EMERG: EMERGENCY("snmp[emerg]: %s\n", msg?msg:slm->msg); break; + case LOG_ALERT: ALERT ("snmp[alert]: %s\n", msg?msg:slm->msg); break; + case LOG_CRIT: CRITICAL ("snmp[crit]: %s\n", msg?msg:slm->msg); break; + case LOG_ERR: ERROR ("snmp[err]: %s\n", msg?msg:slm->msg); break; + case LOG_WARNING: WARNING ("snmp[warning]: %s\n", msg?msg:slm->msg); break; + case LOG_NOTICE: NOTICE ("snmp[notice]: %s\n", msg?msg:slm->msg); break; + case LOG_INFO: INFO ("snmp[info]: %s\n", msg?msg:slm->msg); break; + case LOG_DEBUG: DBGV ("snmp[debug]: %s\n", msg?msg:slm->msg); break; + } + free(msg); + return SNMP_ERR_NOERROR; +} + +static int +getNotifIndex(int eventType) { + + switch (eventType) { + case PTPBASE_NOTIFS_UNEXPECTED_PORT_STATE: + return 1; + case PTPBASE_NOTIFS_EXPECTED_PORT_STATE: + return 2; + case PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_EXCEEDED: + return 3; + case PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_ACCEPTABLE: + return 4; + case PTPBASE_NOTIFS_SLAVE_CLOCK_STEP: + return 5; + case PTPBASE_NOTIFS_SLAVE_NO_SYNC: + return 6; + case PTPBASE_NOTIFS_SLAVE_RECEIVING_SYNC: + return 7; + case PTPBASE_NOTIFS_SLAVE_NO_DELAY: + return 8; + case PTPBASE_NOTIFS_SLAVE_RECEIVING_DELAY: + return 9; + case PTPBASE_NOTIFS_BEST_MASTER_CHANGE: + return 10; + case PTPBASE_NOTIFS_NETWORK_FAULT: + return 11; + case PTPBASE_NOTIFS_NETWORK_FAULT_CLEARED: + return 12; + case PTPBASE_NOTIFS_FREQADJ_FAST: + return 13; + case PTPBASE_NOTIFS_FREQADJ_NORMAL: + return 14; + case PTPBASE_NOTIFS_OFFSET_SECONDS: + return 15; + case PTPBASE_NOTIFS_OFFSET_SUB_SECONDS: + return 16; + case PTPBASE_NOTIFS_TIMEPROPERTIESDS_CHANGE: + return 17; + case PTPBASE_NOTIFS_DOMAIN_MISMATCH: + return 18; + case PTPBASE_NOTIFS_DOMAIN_MISMATCH_CLEARED: + return 19; + default: + return 0; + } +} + +static void +populateNotif (netsnmp_variable_list** varBinds, int eventType, PtpEventData *eventData) { + + switch (eventType) { + case PTPBASE_NOTIFS_UNEXPECTED_PORT_STATE: + case PTPBASE_NOTIFS_EXPECTED_PORT_STATE: + { + oid portStateOid[] = { PTPBASE_MIB_OID, 1, 2, 9, 1, 6, PTPBASE_MIB_INDEX4 }; + + snmp_varlist_add_variable(varBinds, portStateOid, OID_LENGTH(portStateOid), + ASN_INTEGER, (u_char *) &eventData->portDS.portState, sizeof(eventData->portDS.portState)); + } + return; + case PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_EXCEEDED: + case PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_ACCEPTABLE: + { + struct counter64 ofmNum; + Integer64 tmpi64; + internalTime_to_integer64(eventData->currentDS.offsetFromMaster, &tmpi64); + ofmNum.low = htonl(tmpi64.lsb); + ofmNum.high = htonl(tmpi64.msb); + + tmpsnprintf(ofmStr, 64, "%.09f", timeInternalToDouble(&eventData->currentDS.offsetFromMaster)); + + oid ofmOid[] = { PTPBASE_MIB_OID, 1, 2, 1, 1, 5, PTPBASE_MIB_INDEX3 }; + oid ofmStringOid[] = { PTPBASE_MIB_OID, 1, 2, 1, 1, 8, PTPBASE_MIB_INDEX3 }; + oid thresholdOid[] = { PTPBASE_MIB_OID, 1, 2, 1, 1, 9, PTPBASE_MIB_INDEX3 }; + + snmp_varlist_add_variable(varBinds, ofmOid, OID_LENGTH(ofmOid), + ASN_OCTET_STR, (u_char *) &ofmNum, sizeof(ofmNum)); + snmp_varlist_add_variable(varBinds, ofmStringOid, OID_LENGTH(ofmStringOid), + ASN_OCTET_STR, (u_char *) ofmStr, strlen(ofmStr)); + snmp_varlist_add_variable(varBinds, thresholdOid, OID_LENGTH(thresholdOid), + ASN_INTEGER, (u_char *) &eventData->ofmAlarmThreshold, sizeof(snmpRtOpts->ofmAlarmThreshold)); + } + return; + case PTPBASE_NOTIFS_SLAVE_NO_SYNC: + case PTPBASE_NOTIFS_SLAVE_RECEIVING_SYNC: + return; + case PTPBASE_NOTIFS_SLAVE_NO_DELAY: + case PTPBASE_NOTIFS_SLAVE_RECEIVING_DELAY: + return; + case PTPBASE_NOTIFS_BEST_MASTER_CHANGE: + { + oid portIdOid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 4 , PTPBASE_MIB_INDEX3 }; + oid portAddrTypeOid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 14, PTPBASE_MIB_INDEX3 }; + oid portAddrOid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 15, PTPBASE_MIB_INDEX3 }; + oid gmClockIdOid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 8, PTPBASE_MIB_INDEX3 }; + oid gmPriority1Oid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 9, PTPBASE_MIB_INDEX3 }; + oid gmPriority2Oid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 10, PTPBASE_MIB_INDEX3 }; + oid gmClockClassOid[] = { PTPBASE_MIB_OID, 1, 2, 2, 1, 11, PTPBASE_MIB_INDEX3 }; + oid utcOffsetOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 5, PTPBASE_MIB_INDEX3 }; + oid utcOffsetValidOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1, 4, PTPBASE_MIB_INDEX3 }; + oid portStateOid[] = { PTPBASE_MIB_OID, 1, 2, 9, 1, 6, PTPBASE_MIB_INDEX4 }; + + unsigned long addrType = SNMP_IPv4; + unsigned long priority1 = eventData->parentDS.grandmasterPriority1; + unsigned long priority2 = eventData->parentDS.grandmasterPriority2; + unsigned long clockClass = eventData->parentDS.grandmasterClockQuality.clockClass; + unsigned long utcOffset = eventData->timePropertiesDS.currentUtcOffset; + unsigned long utcOffsetValid = TO_TRUTHVALUE(eventData->timePropertiesDS.currentUtcOffsetValid); + + snmp_varlist_add_variable(varBinds, portIdOid, OID_LENGTH(portIdOid), + ASN_OCTET_STR, (u_char *) &eventData->parentDS.parentPortIdentity, sizeof(PortIdentity)); + + snmp_varlist_add_variable(varBinds, portAddrTypeOid, OID_LENGTH(portAddrTypeOid), + ASN_INTEGER, (u_char *) &addrType, sizeof(addrType)); + + snmp_varlist_add_variable(varBinds, portAddrOid, OID_LENGTH(portAddrOid), + ASN_OCTET_STR, (u_char *) &eventData->bestMaster.sourceAddr, sizeof(eventData->bestMaster.sourceAddr)); + + snmp_varlist_add_variable(varBinds, gmClockIdOid, OID_LENGTH(gmClockIdOid), + ASN_OCTET_STR, (u_char *) &eventData->parentDS.grandmasterIdentity, sizeof(ClockIdentity)); + + snmp_varlist_add_variable(varBinds, gmPriority1Oid, OID_LENGTH(gmPriority1Oid), + ASN_UNSIGNED, (u_char *) &priority1, sizeof(priority1)); + + snmp_varlist_add_variable(varBinds, gmPriority2Oid, OID_LENGTH(gmPriority2Oid), + ASN_UNSIGNED, (u_char *) &priority2, sizeof(priority2)); + + snmp_varlist_add_variable(varBinds, gmClockClassOid, OID_LENGTH(gmClockClassOid), + ASN_UNSIGNED, (u_char *) &clockClass, sizeof(clockClass)); + + snmp_varlist_add_variable(varBinds, utcOffsetOid, OID_LENGTH(utcOffsetOid), + ASN_INTEGER, (u_char *) &utcOffset, sizeof(utcOffset)); + + snmp_varlist_add_variable(varBinds, utcOffsetValidOid, OID_LENGTH(utcOffsetValidOid), + ASN_INTEGER, (u_char *) &utcOffsetValid, sizeof(utcOffsetValid)); + + snmp_varlist_add_variable(varBinds, portStateOid, OID_LENGTH(portStateOid), + ASN_INTEGER, (u_char *) &eventData->portDS.portState, sizeof(eventData->portDS.portState)); + } + return; + case PTPBASE_NOTIFS_NETWORK_FAULT: + case PTPBASE_NOTIFS_NETWORK_FAULT_CLEARED: + return; + case PTPBASE_NOTIFS_FREQADJ_FAST: + case PTPBASE_NOTIFS_FREQADJ_NORMAL: + return; + case PTPBASE_NOTIFS_SLAVE_CLOCK_STEP: + case PTPBASE_NOTIFS_OFFSET_SECONDS: + case PTPBASE_NOTIFS_OFFSET_SUB_SECONDS: + { + struct counter64 ofmNum; + Integer64 tmpi64; + internalTime_to_integer64(eventData->currentDS.offsetFromMaster, &tmpi64); + ofmNum.low = htonl(tmpi64.lsb); + ofmNum.high = htonl(tmpi64.msb); + + tmpsnprintf(ofmStr, 64, "%.09f", timeInternalToDouble(&eventData->currentDS.offsetFromMaster)); + + oid ofmOid[] = { PTPBASE_MIB_OID, 1, 2, 1, 1, 5, PTPBASE_MIB_INDEX3 }; + oid ofmStringOid[] = { PTPBASE_MIB_OID, 1, 2, 1, 1, 8, PTPBASE_MIB_INDEX3 }; + + snmp_varlist_add_variable(varBinds, ofmOid, OID_LENGTH(ofmOid), + ASN_OCTET_STR, (u_char *) &ofmNum, sizeof(ofmNum)); + snmp_varlist_add_variable(varBinds, ofmStringOid, OID_LENGTH(ofmStringOid), + ASN_OCTET_STR, (u_char *) ofmStr, strlen(ofmStr)); + } + return; + + case PTPBASE_NOTIFS_TIMEPROPERTIESDS_CHANGE: + { + oid utcOffsetValidOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1, 4, PTPBASE_MIB_INDEX3 }; + oid utcOffsetOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 5, PTPBASE_MIB_INDEX3 }; + oid leap59Oid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 6, PTPBASE_MIB_INDEX3 }; + oid leap61Oid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 7, PTPBASE_MIB_INDEX3 }; + oid timeTraceableOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 8, PTPBASE_MIB_INDEX3 }; + oid frequencyTraceableOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 9, PTPBASE_MIB_INDEX3 }; + oid ptpTimescaleOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 10, PTPBASE_MIB_INDEX3 }; + oid timeSourceOid[] = { PTPBASE_MIB_OID, 1, 2, 5, 1 , 11, PTPBASE_MIB_INDEX3 }; + + unsigned long utcOffset = eventData->timePropertiesDS.currentUtcOffset; + unsigned long utcOffsetValid = TO_TRUTHVALUE(eventData->timePropertiesDS.currentUtcOffsetValid); + unsigned long leap59 = TO_TRUTHVALUE(eventData->timePropertiesDS.leap61); + unsigned long leap61 = TO_TRUTHVALUE(eventData->timePropertiesDS.leap61); + unsigned long timeTraceable = TO_TRUTHVALUE(eventData->timePropertiesDS.timeTraceable); + unsigned long frequencyTraceable = TO_TRUTHVALUE(eventData->timePropertiesDS.frequencyTraceable); + unsigned long ptpTimescale = TO_TRUTHVALUE(eventData->timePropertiesDS.ptpTimescale); + unsigned long timeSource = eventData->timePropertiesDS.timeSource; + + snmp_varlist_add_variable(varBinds, utcOffsetValidOid, OID_LENGTH(utcOffsetValidOid), + ASN_INTEGER, (u_char *) &utcOffsetValid, sizeof(utcOffsetValid)); + + snmp_varlist_add_variable(varBinds, utcOffsetOid, OID_LENGTH(utcOffsetOid), + ASN_INTEGER, (u_char *) &utcOffset, sizeof(utcOffset)); + + snmp_varlist_add_variable(varBinds, leap59Oid, OID_LENGTH(leap59Oid), + ASN_INTEGER, (u_char *) &leap59, sizeof(leap59)); + + snmp_varlist_add_variable(varBinds, leap61Oid, OID_LENGTH(leap61Oid), + ASN_INTEGER, (u_char *) &leap61, sizeof(leap61)); + + snmp_varlist_add_variable(varBinds, timeTraceableOid, OID_LENGTH(timeTraceableOid), + ASN_INTEGER, (u_char *) &timeTraceable, sizeof(timeTraceable)); + + snmp_varlist_add_variable(varBinds, frequencyTraceableOid, OID_LENGTH(frequencyTraceableOid), + ASN_INTEGER, (u_char *) &frequencyTraceable, sizeof(frequencyTraceable)); + + snmp_varlist_add_variable(varBinds, ptpTimescaleOid, OID_LENGTH(ptpTimescaleOid), + ASN_INTEGER, (u_char *) &ptpTimescale, sizeof(ptpTimescale)); + + snmp_varlist_add_variable(varBinds, ptpTimescaleOid, OID_LENGTH(ptpTimescaleOid), + ASN_INTEGER, (u_char *) &ptpTimescale, sizeof(ptpTimescale)); + + snmp_varlist_add_variable(varBinds, timeSourceOid, OID_LENGTH(timeSourceOid), + ASN_INTEGER, (u_char *) &timeSource, sizeof(timeSource)); + + } + return; + case PTPBASE_NOTIFS_DOMAIN_MISMATCH: + { + oid domainNumberOid[] = { PTPBASE_MIB_OID, 1, 2, 3, 1, 12, PTPBASE_MIB_INDEX3 }; + oid lastMismatchedDomainOid[] = { PTPBASE_MIB_OID, 1, 2, 8, 1, 17, PTPBASE_MIB_INDEX4 }; + unsigned long domainNumber = eventData->defaultDS.domainNumber; + unsigned long lastMismatchedDomain = eventData->portDS.lastMismatchedDomain; + snmp_varlist_add_variable(varBinds, domainNumberOid, OID_LENGTH(domainNumberOid), + ASN_INTEGER, (u_char *) &domainNumber, sizeof(domainNumber)); + snmp_varlist_add_variable(varBinds, lastMismatchedDomainOid, OID_LENGTH(lastMismatchedDomainOid), + ASN_INTEGER, (u_char *) &lastMismatchedDomain, sizeof(lastMismatchedDomain)); + + } + return; + case PTPBASE_NOTIFS_DOMAIN_MISMATCH_CLEARED: + { + oid domainNumberOid[] = { PTPBASE_MIB_OID, 1, 2, 3, 1, 12, PTPBASE_MIB_INDEX3 }; + unsigned long domainNumber = eventData->defaultDS.domainNumber; + snmp_varlist_add_variable(varBinds, domainNumberOid, OID_LENGTH(domainNumberOid), + ASN_INTEGER, (u_char *) &domainNumber, sizeof(domainNumber)); + } + return; + default: + return; + } + +} + +static void +sendNotif(int eventType, PtpEventData *eventData) { + + /* snmpTrapOID.0 */ + oid trapOid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; + netsnmp_variable_list *varBinds = NULL; + + int notifIndex = getNotifIndex(eventType); + + if(!notifIndex) { + DBG("SNMP trap request for unknown event type: %d\n", eventType); + return; + } + + /* the notification oid*/ + oid notifOid[] = { PTPBASE_MIB_OID, 0, (oid)notifIndex }; + + /* add notification oid */ + snmp_varlist_add_variable(&varBinds, trapOid, OID_LENGTH(trapOid), + ASN_OBJECT_ID, (u_char *) notifOid, OID_LENGTH(notifOid) * sizeof(oid)); + + /* add the accompanying varbinds */ + populateNotif(&varBinds, eventType, eventData); + + send_v2trap(varBinds); + + snmp_free_varbind(varBinds); + +} + + +/** + * Initialisation of SNMP subsystem. + */ +void +snmpInit(RunTimeOpts *rtOpts, PtpClock *ptpClock) { + netsnmp_enable_subagent(); + snmp_disable_log(); + snmp_enable_calllog(); + snmp_register_callback(SNMP_CALLBACK_LIBRARY, + SNMP_CALLBACK_LOGGING, + snmpLogCallback, + NULL); + init_agent("ptpAgent"); + REGISTER_MIB("ptpMib", snmpVariables, variable7, ptp_oid); + init_snmp("ptpAgent"); + + /* Currently, ptpd only handles one clock. We put it in a + * global variable for the need of our subsystem. */ + snmpPtpClock = ptpClock; + snmpRtOpts = rtOpts; + +} + +/** + * Clean up and shut down the SNMP subagent + */ + +void +snmpShutdown() { + unregister_mib(ptp_oid, sizeof(ptp_oid) / sizeof(oid)); + snmp_shutdown("ptpMib"); + SOCK_CLEANUP; + +} + +void +alarmHandler_snmp(AlarmEntry *alarm) +{ + int notifId = -1; + + if(alarm->state == ALARM_SET) { + DBG("[snmp] Alarm %s set trap processed\n", alarm->name); + switch(alarm->id) { + case ALRM_PORT_STATE: + notifId = PTPBASE_NOTIFS_UNEXPECTED_PORT_STATE; + break; + case ALRM_OFM_THRESHOLD: + notifId = PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_EXCEEDED; + break; + case ALRM_OFM_SECONDS: + notifId = PTPBASE_NOTIFS_OFFSET_SECONDS; + break; + case ALRM_NO_SYNC: + notifId = PTPBASE_NOTIFS_SLAVE_NO_SYNC; + break; + case ALRM_NO_DELAY: + notifId = PTPBASE_NOTIFS_SLAVE_NO_DELAY; + break; + case ALRM_NETWORK_FLT: + notifId = PTPBASE_NOTIFS_NETWORK_FAULT; + break; + case ALRM_FAST_ADJ: + notifId = PTPBASE_NOTIFS_FREQADJ_FAST; + break; + case ALRM_DOMAIN_MISMATCH: + notifId = PTPBASE_NOTIFS_DOMAIN_MISMATCH; + break; + } + } + + if(alarm->state == ALARM_UNSET) { + DBG("[snmp] Alarm %s clear trap processed\n", alarm->name); + switch(alarm->id) { + case ALRM_PORT_STATE: + notifId = PTPBASE_NOTIFS_EXPECTED_PORT_STATE; + break; + case ALRM_OFM_THRESHOLD: + notifId = PTPBASE_NOTIFS_SLAVE_OFFSET_THRESHOLD_ACCEPTABLE; + break; + case ALRM_OFM_SECONDS: + notifId = PTPBASE_NOTIFS_OFFSET_SUB_SECONDS; + break; + case ALRM_NO_SYNC: + notifId = PTPBASE_NOTIFS_SLAVE_RECEIVING_SYNC; + break; + case ALRM_NO_DELAY: + notifId = PTPBASE_NOTIFS_SLAVE_RECEIVING_DELAY; + break; + case ALRM_NETWORK_FLT: + notifId = PTPBASE_NOTIFS_NETWORK_FAULT_CLEARED; + break; + case ALRM_FAST_ADJ: + notifId = PTPBASE_NOTIFS_FREQADJ_NORMAL; + break; + case ALRM_DOMAIN_MISMATCH: + notifId = PTPBASE_NOTIFS_DOMAIN_MISMATCH_CLEARED; + break; + } + } + + if(alarm->eventOnly) { + DBG("[snmp] Event %s notification processed\n", alarm->name); + switch(alarm->id) { + case ALRM_CLOCK_STEP: + notifId = PTPBASE_NOTIFS_SLAVE_CLOCK_STEP; + break; + case ALRM_MASTER_CHANGE: + notifId = PTPBASE_NOTIFS_BEST_MASTER_CHANGE; + break; + case ALRM_TIMEPROP_CHANGE: + notifId = PTPBASE_NOTIFS_TIMEPROPERTIESDS_CHANGE; + break; + default: + break; + } + } + + + if(notifId >= 0) { + sendNotif(notifId, &alarm->eventData); + return; + } + + DBG("Unhandled SNMP event id 0x%x\n", alarm->id); + +} diff --git a/src/ptpd/src/dep/startup.c b/src/ptpd/src/dep/startup.c new file mode 100644 index 0000000..57e2c6f --- /dev/null +++ b/src/ptpd/src/dep/startup.c @@ -0,0 +1,1025 @@ +/*- + * Copyright (c) 2014-2015 Wojciech Owczarek, + * Copyright (c) 2012-2013 George V. Neville-Neil, + * Wojciech Owczarek + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file startup.c + * @date Wed Jun 23 09:33:27 2010 + * + * @brief Code to handle daemon startup, including command line args + * + * The function in this file are called when the daemon starts up + * and include the getopt() command line argument parsing. + */ + +#include "../ptpd.h" + +/* + * valgrind 3.5.0 currently reports no errors (last check: 20110512) + * valgrind 3.4.1 lacks an adjtimex handler + * + * to run: sudo valgrind --show-reachable=yes --leak-check=full --track-origins=yes -- ./ptpd2 -c ... + */ + +/* + to test daemon locking and startup sequence itself, try: + + function s() { set -o pipefail ; eval "$@" | sed 's/^/\t/' ; echo $?; } + sudo killall ptpd2 + s ./ptpd2 + s sudo ./ptpd2 + s sudo ./ptpd2 -t -g + s sudo ./ptpd2 -t -g -b eth0 + s sudo ./ptpd2 -t -g -b eth0 + ps -ef | grep ptpd2 +*/ + +/* + * Synchronous signal processing: + * original idea: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/ntpd/ntpd.c?rev=1.68;content-type=text%2Fplain + */ +volatile sig_atomic_t sigint_received = 0; +volatile sig_atomic_t sigterm_received = 0; +volatile sig_atomic_t sighup_received = 0; +volatile sig_atomic_t sigusr1_received = 0; +volatile sig_atomic_t sigusr2_received = 0; + +/* Pointer to the current lock file */ +FILE* G_lockFilePointer; + +/* + * Function to catch signals asynchronously. + * Assuming that the daemon periodically calls checkSignals(), then all operations are safely done synchrously at a later opportunity. + * + * Please do NOT call any functions inside this handler - especially DBG() and its friends, or any glibc. + */ +void catchSignals(int sig) +{ + switch (sig) { + case SIGINT: + sigint_received = 1; + break; + case SIGTERM: + sigterm_received = 1; + break; + case SIGHUP: + sighup_received = 1; + break; + case SIGUSR1: + sigusr1_received = 1; + break; + case SIGUSR2: + sigusr2_received = 1; + break; + default: + /* + * TODO: should all other signals be catched, and handled as SIGINT? + * + * Reason: currently, all other signals are just uncatched, and the OS kills us. + * The difference is that we could then close the open files properly. + */ + break; + } +} + +/* + * exit the program cleanly + */ +void +do_signal_close(PtpClock * ptpClock) +{ + + timingDomain.shutdown(&timingDomain); + + NOTIFY("Shutdown on close signal\n"); + exit(0); +} + +void +applyConfig(dictionary *baseConfig, RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + Boolean reloadSuccessful = TRUE; + + + /* Load default config to fill in the blanks in the config file */ + RunTimeOpts tmpOpts; + loadDefaultSettings(&tmpOpts); + + /* Check the new configuration for errors, fill in the blanks from defaults */ + if( ( rtOpts->candidateConfig = parseConfig(CFGOP_PARSE, NULL, baseConfig, &tmpOpts)) == NULL ) { + WARNING("Configuration has errors, reload aborted\n"); + return; + } + + /* Check for changes between old and new configuration */ + if(compareConfig(rtOpts->candidateConfig,rtOpts->currentConfig)) { + INFO("Configuration unchanged\n"); + goto cleanup; + } + + /* + * Mark which subsystems have to be restarted. Most of this will be picked up by doState() + * If there are errors past config correctness (such as non-existent NIC, + * or lock file clashes if automatic lock files used - abort the mission + */ + + rtOpts->restartSubsystems = + checkSubsystemRestart(rtOpts->candidateConfig, rtOpts->currentConfig, rtOpts); + + /* If we're told to re-check lock files, do it: tmpOpts already has what rtOpts should */ + if( (rtOpts->restartSubsystems & PTPD_CHECK_LOCKS) && + tmpOpts.autoLockFile && !checkOtherLocks(&tmpOpts)) { + reloadSuccessful = FALSE; + } + + /* If the network configuration has changed, check if the interface is OK */ + if(rtOpts->restartSubsystems & PTPD_RESTART_NETWORK) { + INFO("Network configuration changed - checking interface(s)\n"); + if(!testInterface(tmpOpts.primaryIfaceName, &tmpOpts)) { + reloadSuccessful = FALSE; + ERROR("Error: Cannot use %s interface\n",tmpOpts.primaryIfaceName); + } + if(rtOpts->backupIfaceEnabled && !testInterface(tmpOpts.backupIfaceName, &tmpOpts)) { + rtOpts->restartSubsystems = -1; + ERROR("Error: Cannot use %s interface as backup\n",tmpOpts.backupIfaceName); + } + } +#if (defined(linux) && defined(HAVE_SCHED_H)) || defined(HAVE_SYS_CPUSET_H) || defined(__QNXNTO__) + /* Changing the CPU affinity mask */ + if(rtOpts->restartSubsystems & PTPD_CHANGE_CPUAFFINITY) { + NOTIFY("Applying CPU binding configuration: changing selected CPU core\n"); + + if(setCpuAffinity(tmpOpts.cpuNumber) < 0) { + if(tmpOpts.cpuNumber == -1) { + ERROR("Could not unbind from CPU core %d\n", rtOpts->cpuNumber); + } else { + ERROR("Could bind to CPU core %d\n", tmpOpts.cpuNumber); + } + reloadSuccessful = FALSE; + } else { + if(tmpOpts.cpuNumber > -1) + INFO("Successfully bound "PTPD_PROGNAME" to CPU core %d\n", tmpOpts.cpuNumber); + else + INFO("Successfully unbound "PTPD_PROGNAME" from cpu core CPU core %d\n", rtOpts->cpuNumber); + } + } +#endif + + if(!reloadSuccessful) { + ERROR("New configuration cannot be applied - aborting reload\n"); + rtOpts->restartSubsystems = 0; + goto cleanup; + } + + + /** + * Commit changes to rtOpts and currentConfig + * (this should never fail as the config has already been checked if we're here) + * However if this DOES fail, some default has been specified out of range - + * this is the only situation where parse will succeed but commit not: + * disable quiet mode to show what went wrong, then die. + */ + if (rtOpts->currentConfig) { + dictionary_del(&rtOpts->currentConfig); + } + if ( (rtOpts->currentConfig = parseConfig(CFGOP_PARSE_QUIET, NULL, rtOpts->candidateConfig,rtOpts)) == NULL) { + CRITICAL("************ "PTPD_PROGNAME": parseConfig returned NULL during config commit" + " - this is a BUG - report the following: \n"); + + if ((rtOpts->currentConfig = parseConfig(CFGOP_PARSE, NULL, rtOpts->candidateConfig,rtOpts)) == NULL) + CRITICAL("*****************" PTPD_PROGNAME" shutting down **********************\n"); + /* + * Could be assert(), but this should be done any time this happens regardless of + * compile options. Anyhow, if we're here, the daemon will no doubt segfault soon anyway + */ + abort(); + } + + /* clean up */ + cleanup: + + dictionary_del(&rtOpts->candidateConfig); +} + + +/** + * Signal handler for HUP which tells us to swap the log file + * and reload configuration file if specified + * + * @param sig + */ +void +do_signal_sighup(RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + + + + NOTIFY("SIGHUP received\n"); + +#ifdef RUNTIME_DEBUG + if(rtOpts->transport == UDP_IPV4 && rtOpts->ipMode != IPMODE_UNICAST) { + DBG("SIGHUP - running an ipv4 multicast based mode, re-sending IGMP joins\n"); + netRefreshIGMP(&ptpClock->netPath, rtOpts, ptpClock); + } +#endif /* RUNTIME_DEBUG */ + + + /* if we don't have a config file specified, we're done - just reopen log files*/ + if(strlen(rtOpts->configFile) != 0) { + + dictionary* tmpConfig = dictionary_new(0); + + /* Try reloading the config file */ + NOTIFY("Reloading configuration file: %s\n",rtOpts->configFile); + + if(!loadConfigFile(&tmpConfig, rtOpts)) { + + dictionary_del(&tmpConfig); + + } else { + dictionary_merge(rtOpts->cliConfig, tmpConfig, 1, 1, "from command line"); + applyConfig(tmpConfig, rtOpts, ptpClock); + dictionary_del(&tmpConfig); + + } + + } + + /* tell the service it can perform any HUP-triggered actions */ + ptpClock->timingService.reloadRequested = TRUE; + + if(rtOpts->recordLog.logEnabled || + rtOpts->eventLog.logEnabled || + (rtOpts->statisticsLog.logEnabled)) + INFO("Reopening log files\n"); + + restartLogging(rtOpts); + + if(rtOpts->statisticsLog.logEnabled) + ptpClock->resetStatisticsLog = TRUE; + + +} + +void +restartSubsystems(RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + DBG("RestartSubsystems: %d\n",rtOpts->restartSubsystems); + /* So far, PTP_INITIALIZING is required for both network and protocol restart */ + if((rtOpts->restartSubsystems & PTPD_RESTART_PROTOCOL) || + (rtOpts->restartSubsystems & PTPD_RESTART_NETWORK)) { + + if(rtOpts->restartSubsystems & PTPD_RESTART_NETWORK) { + NOTIFY("Applying network configuration: going into PTP_INITIALIZING\n"); + } + + /* These parameters have to be passed to ptpClock before re-init */ + ptpClock->defaultDS.clockQuality.clockClass = rtOpts->clockQuality.clockClass; + ptpClock->defaultDS.slaveOnly = rtOpts->slaveOnly; + ptpClock->disabled = rtOpts->portDisabled; + + if(rtOpts->restartSubsystems & PTPD_RESTART_PROTOCOL) { + INFO("Applying protocol configuration: going into %s\n", + ptpClock->disabled ? "PTP_DISABLED" : "PTP_INITIALIZING"); + } + + /* Move back to primary interface only during configuration changes. */ + ptpClock->runningBackupInterface = FALSE; + toState(ptpClock->disabled ? PTP_DISABLED : PTP_INITIALIZING, rtOpts, ptpClock); + + } else { + /* Nothing happens here for now - SIGHUP handler does this anyway */ + if(rtOpts->restartSubsystems & PTPD_UPDATE_DATASETS) { + NOTIFY("Applying PTP engine configuration: updating datasets\n"); + updateDatasets(ptpClock, rtOpts); + }} + /* Nothing happens here for now - SIGHUP handler does this anyway */ + if(rtOpts->restartSubsystems & PTPD_RESTART_LOGGING) { + NOTIFY("Applying logging configuration: restarting logging\n"); + } + + + if(rtOpts->restartSubsystems & PTPD_RESTART_ACLS) { + NOTIFY("Applying access control list configuration\n"); + /* re-compile ACLs */ + freeIpv4AccessList(&ptpClock->netPath.timingAcl); + freeIpv4AccessList(&ptpClock->netPath.managementAcl); + if(rtOpts->timingAclEnabled) { + ptpClock->netPath.timingAcl=createIpv4AccessList(rtOpts->timingAclPermitText, + rtOpts->timingAclDenyText, rtOpts->timingAclOrder); + } + if(rtOpts->managementAclEnabled) { + ptpClock->netPath.managementAcl=createIpv4AccessList(rtOpts->managementAclPermitText, + rtOpts->managementAclDenyText, rtOpts->managementAclOrder); + } + } + + if(rtOpts->restartSubsystems & PTPD_RESTART_ALARMS) { + NOTIFY("Applying alarm configuration\n"); + configureAlarms(ptpClock->alarms, ALRM_MAX, (void*)ptpClock); + } + +#ifdef PTPD_STATISTICS + /* Reinitialising the outlier filter containers */ + if(rtOpts->restartSubsystems & PTPD_RESTART_FILTERS) { + + NOTIFY("Applying filter configuration: re-initialising filters\n"); + + freeDoubleMovingStatFilter(&ptpClock->filterMS); + freeDoubleMovingStatFilter(&ptpClock->filterSM); + + ptpClock->oFilterMS.shutdown(&ptpClock->oFilterMS); + ptpClock->oFilterSM.shutdown(&ptpClock->oFilterSM); + + outlierFilterSetup(&ptpClock->oFilterMS); + outlierFilterSetup(&ptpClock->oFilterSM); + + ptpClock->oFilterMS.init(&ptpClock->oFilterMS,&rtOpts->oFilterMSConfig, "delayMS"); + ptpClock->oFilterSM.init(&ptpClock->oFilterSM,&rtOpts->oFilterSMConfig, "delaySM"); + + + if(rtOpts->filterMSOpts.enabled) { + ptpClock->filterMS = createDoubleMovingStatFilter(&rtOpts->filterMSOpts,"delayMS"); + } + + if(rtOpts->filterSMOpts.enabled) { + ptpClock->filterSM = createDoubleMovingStatFilter(&rtOpts->filterSMOpts, "delaySM"); + } + + } +#endif /* PTPD_STATISTICS */ + + + ptpClock->timingService.reloadRequested = TRUE; + + if(rtOpts->restartSubsystems & PTPD_RESTART_NTPENGINE && timingDomain.serviceCount > 1) { + ptpClock->ntpControl.timingService.shutdown(&ptpClock->ntpControl.timingService); + } + + if((rtOpts->restartSubsystems & PTPD_RESTART_NTPENGINE) || + (rtOpts->restartSubsystems & PTPD_RESTART_NTPCONFIG)) { + ntpSetup(rtOpts, ptpClock); + } + if((rtOpts->restartSubsystems & PTPD_RESTART_NTPENGINE) && rtOpts->ntpOptions.enableEngine) { + timingServiceSetup(&ptpClock->ntpControl.timingService); + ptpClock->ntpControl.timingService.init(&ptpClock->ntpControl.timingService); + } + + ptpClock->timingService.dataSet.priority1 = rtOpts->preferNTP; + + timingDomain.electionDelay = rtOpts->electionDelay; + if(timingDomain.electionLeft > timingDomain.electionDelay) { + timingDomain.electionLeft = timingDomain.electionDelay; + } + + timingDomain.services[0]->holdTime = rtOpts->ntpOptions.failoverTimeout; + + if(timingDomain.services[0]->holdTimeLeft > + timingDomain.services[0]->holdTime) { + timingDomain.services[0]->holdTimeLeft = + rtOpts->ntpOptions.failoverTimeout; + } + + ptpClock->timingService.timeout = rtOpts->idleTimeout; + + /* Update PI servo parameters */ + setupPIservo(&ptpClock->servo, rtOpts); + /* Config changes don't require subsystem restarts - acknowledge it */ + if(rtOpts->restartSubsystems == PTPD_RESTART_NONE) { + NOTIFY("Applying configuration\n"); + } + + if(rtOpts->restartSubsystems != -1) + rtOpts->restartSubsystems = 0; + +} + + +/* + * Synchronous signal processing: + * This function should be called regularly from the main loop + */ +void +checkSignals(RunTimeOpts * rtOpts, PtpClock * ptpClock) +{ + /* + * note: + * alarm signals are handled in a similar way in dep/timer.c + */ + + if(sigint_received || sigterm_received){ + do_signal_close(ptpClock); + } + + if(sighup_received){ + do_signal_sighup(rtOpts, ptpClock); + sighup_received=0; + } + + if(sigusr1_received){ + if(ptpClock->portDS.portState == PTP_SLAVE){ + WARNING("SIGUSR1 received, stepping clock to current known OFM\n"); + stepClock(rtOpts, ptpClock); +// ptpClock->clockControl.stepRequired = TRUE; + } else { + ERROR("SIGUSR1 received - will not step clock, not in PTP_SLAVE state\n"); + } + sigusr1_received = 0; + } + + if(sigusr2_received){ + +/* testing only: testing step detection */ +#if 0 + { + ptpClock->addOffset ^= 1; + INFO("a: %d\n", ptpClock->addOffset); + sigusr2_received = 0; + return; + } +#endif + displayCounters(ptpClock); + displayAlarms(ptpClock->alarms, ALRM_MAX); + if(rtOpts->timingAclEnabled) { + INFO("\n\n"); + INFO("** Timing message ACL:\n"); + dumpIpv4AccessList(ptpClock->netPath.timingAcl); + } + if(rtOpts->managementAclEnabled) { + INFO("\n\n"); + INFO("** Management message ACL:\n"); + dumpIpv4AccessList(ptpClock->netPath.managementAcl); + } + if(rtOpts->clearCounters) { + clearCounters(ptpClock); + NOTIFY("PTP engine counters cleared\n"); + } +#ifdef PTPD_STATISTICS + if(rtOpts->oFilterSMConfig.enabled) { + ptpClock->oFilterSM.display(&ptpClock->oFilterSM); + } + if(rtOpts->oFilterMSConfig.enabled) { + ptpClock->oFilterMS.display(&ptpClock->oFilterMS); + } +#endif /* PTPD_STATISTICS */ + sigusr2_received = 0; + } + +} + +#ifdef RUNTIME_DEBUG +/* These functions are useful to temporarily enable Debug around parts of code, similar to bash's "set -x" */ +void enable_runtime_debug(void ) +{ + extern RunTimeOpts rtOpts; + + rtOpts.debug_level = max(LOG_DEBUGV, rtOpts.debug_level); +} + +void disable_runtime_debug(void ) +{ + extern RunTimeOpts rtOpts; + + rtOpts.debug_level = LOG_INFO; +} +#endif + +int +writeLockFile(RunTimeOpts * rtOpts) +{ + + int lockPid = 0; + + DBGV("Checking lock file: %s\n", rtOpts->lockFile); + + if ( (G_lockFilePointer=fopen(rtOpts->lockFile, "w+")) == NULL) { + PERROR("Could not open lock file %s for writing", rtOpts->lockFile); + return(0); + } + if (lockFile(fileno(G_lockFilePointer)) < 0) { + if ( checkLockStatus(fileno(G_lockFilePointer), + DEFAULT_LOCKMODE, &lockPid) == 0) { + ERROR("Another "PTPD_PROGNAME" instance is running: %s locked by PID %d\n", + rtOpts->lockFile, lockPid); + } else { + PERROR("Could not acquire lock on %s:", rtOpts->lockFile); + } + goto failure; + } + if(ftruncate(fileno(G_lockFilePointer), 0) == -1) { + PERROR("Could not truncate %s: %s", + rtOpts->lockFile, strerror(errno)); + goto failure; + } + if ( fprintf(G_lockFilePointer, "%ld\n", (long)getpid()) == -1) { + PERROR("Could not write to lock file %s: %s", + rtOpts->lockFile, strerror(errno)); + goto failure; + } + INFO("Successfully acquired lock on %s\n", rtOpts->lockFile); + fflush(G_lockFilePointer); + return(1); + failure: + fclose(G_lockFilePointer); + return(0); + +} + +void +ptpdShutdown(PtpClock * ptpClock) +{ + + extern RunTimeOpts rtOpts; + + /* + * go into DISABLED state so the FSM can call any PTP-specific shutdown actions, + * such as canceling unicast transmission + */ + toState(PTP_DISABLED, &rtOpts, ptpClock); + /* process any outstanding events before exit */ + updateAlarms(ptpClock->alarms, ALRM_MAX); + netShutdown(&ptpClock->netPath); + free(ptpClock->foreign); + + /* free management and signaling messages, they can have dynamic memory allocated */ + if(ptpClock->msgTmpHeader.messageType == MANAGEMENT) + freeManagementTLV(&ptpClock->msgTmp.manage); + freeManagementTLV(&ptpClock->outgoingManageTmp); + if(ptpClock->msgTmpHeader.messageType == SIGNALING) + freeSignalingTLV(&ptpClock->msgTmp.signaling); + freeSignalingTLV(&ptpClock->outgoingSignalingTmp); + +#ifdef PTPD_SNMP + snmpShutdown(); +#endif /* PTPD_SNMP */ + +#ifndef PTPD_STATISTICS + /* Not running statistics code - write observed drift to driftfile if enabled, inform user */ + if(ptpClock->defaultDS.slaveOnly && !ptpClock->servo.runningMaxOutput) + saveDrift(ptpClock, &rtOpts, FALSE); +#else + ptpClock->oFilterMS.shutdown(&ptpClock->oFilterMS); + ptpClock->oFilterSM.shutdown(&ptpClock->oFilterSM); + freeDoubleMovingStatFilter(&ptpClock->filterMS); + freeDoubleMovingStatFilter(&ptpClock->filterSM); + + /* We are running statistics code - save drift on exit only if we're not monitoring servo stability */ + if(!rtOpts.servoStabilityDetection && !ptpClock->servo.runningMaxOutput) + saveDrift(ptpClock, &rtOpts, FALSE); +#endif /* PTPD_STATISTICS */ + + if (rtOpts.currentConfig != NULL) + dictionary_del(&rtOpts.currentConfig); + if(rtOpts.cliConfig != NULL) + dictionary_del(&rtOpts.cliConfig); + + timerShutdown(ptpClock->timers); + + free(ptpClock); + ptpClock = NULL; + + extern PtpClock* G_ptpClock; + G_ptpClock = NULL; + + + + /* properly clean lockfile (eventough new deaemons can acquire the lock after we die) */ + if(!rtOpts.ignore_daemon_lock && G_lockFilePointer != NULL) { + fclose(G_lockFilePointer); + G_lockFilePointer = NULL; + } + unlink(rtOpts.lockFile); + + if(rtOpts.statusLog.logEnabled) { + /* close and remove the status file */ + if(rtOpts.statusLog.logFP != NULL) { + fclose(rtOpts.statusLog.logFP); + rtOpts.statusLog.logFP = NULL; + } + unlink(rtOpts.statusLog.logPath); + } + + stopLogging(&rtOpts); + +} + +void dump_command_line_parameters(int argc, char **argv) +{ + + int i = 0; + char sbuf[1000]; + char *st = sbuf; + int len = 0; + + *st = '\0'; + for(i=0; i < argc; i++){ + if(strcmp(argv[i],"") == 0) + continue; + len += snprintf(sbuf + len, + sizeof(sbuf) - len, + "%s ", argv[i]); + } + INFO("Starting %s daemon with parameters: %s\n", PTPD_PROGNAME, sbuf); +} + + + +PtpClock * +ptpdStartup(int argc, char **argv, Integer16 * ret, RunTimeOpts * rtOpts) +{ + PtpClock * ptpClock; + TimeInternal tmpTime; + int i = 0; + + /* + * Set the default mode for all newly created files - previously + * this was not the case for log files. This adds consistency + * and allows to use FILE* vs. fds everywhere + */ + umask(~DEFAULT_FILE_PERMS); + + /* get some entropy in... */ + getTime(&tmpTime); + srand(tmpTime.seconds ^ tmpTime.nanoseconds); + + /** + * If a required setting, such as interface name, or a setting + * requiring a range check is to be set via getopts_long, + * the respective currentConfig dictionary entry should be set, + * instead of just setting the rtOpts field. + * + * Config parameter evaluation priority order: + * 1. Any dictionary keys set in the getopt_long loop + * 2. CLI long section:key type options + * 3. Any built-in config templates + * 4. Any templates loaded from template file + * 5. Config file (parsed last), merged with 2. and 3 - will be overwritten by CLI options + * 6. Defaults and any rtOpts fields set in the getopt_long loop + **/ + + /** + * Load defaults. Any options set here and further inside loadCommandLineOptions() + * by setting rtOpts fields, will be considered the defaults + * for config file and section:key long options. + */ + loadDefaultSettings(rtOpts); + /* initialise the config dictionary */ + rtOpts->candidateConfig = dictionary_new(0); + rtOpts->cliConfig = dictionary_new(0); + + /* parse all long section:key options and clean up argv for getopt */ + loadCommandLineKeys(rtOpts->cliConfig,argc,argv); + /* parse the normal short and long options, exit on error */ + if (!loadCommandLineOptions(rtOpts, rtOpts->cliConfig, argc, argv, ret)) { + goto fail; + } + + /* Display startup info and argv if not called with -? or -H */ + NOTIFY("%s version %s starting\n",USER_DESCRIPTION, USER_VERSION); + dump_command_line_parameters(argc, argv); + /* + * we try to catch as many error conditions as possible, but before we call daemon(). + * the exception is the lock file, as we get a new pid when we call daemon(), + * so this is checked twice: once to read, second to read/write + */ +// ##### SCORE MODIFICATION BEGIN ##### + /* The security requirements mandate that we run the process with non-root access. + Therefore, we are commenting out this piece of code that restricts us to running with root access. + */ + // if(geteuid() != 0) + // { + // printf("Error: "PTPD_PROGNAME" daemon can only be run as root\n"); + // *ret = 1; + // goto fail; + // } +// ##### SCORE MODIFICATION END ##### + + /* Have we got a config file? */ + if(strlen(rtOpts->configFile) > 0) { + /* config file settings overwrite all others, except for empty strings */ + INFO("Loading configuration file: %s\n",rtOpts->configFile); + if(loadConfigFile(&rtOpts->candidateConfig, rtOpts)) { + dictionary_merge(rtOpts->cliConfig, rtOpts->candidateConfig, 1, 1, "from command line"); + } else { + *ret = 1; + dictionary_merge(rtOpts->cliConfig, rtOpts->candidateConfig, 1, 1, "from command line"); + goto configcheck; + } + } else { + dictionary_merge(rtOpts->cliConfig, rtOpts->candidateConfig, 1, 1, "from command line"); + } + /** + * This is where the final checking of the candidate settings container happens. + * A dictionary is returned with only the known options, explicitly set to defaults + * if not present. NULL is returned on any config error - parameters missing, out of range, + * etc. The getopt loop in loadCommandLineOptions() only sets keys verified here. + */ + if( ( rtOpts->currentConfig = parseConfig(CFGOP_PARSE, NULL, rtOpts->candidateConfig,rtOpts)) == NULL ) { + *ret = 1; + dictionary_del(&rtOpts->candidateConfig); + goto configcheck; + } + + /* we've been told to print the lock file and exit cleanly */ + if(rtOpts->printLockFile) { + printf("%s\n", rtOpts->lockFile); + *ret = 0; + goto fail; + } + + /* we don't need the candidate config any more */ + dictionary_del(&rtOpts->candidateConfig); + + /* Check network before going into background */ + if(!testInterface(rtOpts->primaryIfaceName, rtOpts)) { + ERROR("Error: Cannot use %s interface\n",rtOpts->primaryIfaceName); + *ret = 1; + goto configcheck; + } + if(rtOpts->backupIfaceEnabled && !testInterface(rtOpts->backupIfaceName, rtOpts)) { + ERROR("Error: Cannot use %s interface as backup\n",rtOpts->backupIfaceName); + *ret = 1; + goto configcheck; + } + + +configcheck: + /* + * We've been told to check config only - clean exit before checking locks + */ + if(rtOpts->checkConfigOnly) { + if(*ret != 0) { + printf("Configuration has errors\n"); + *ret = 1; + } + else + printf("Configuration OK\n"); + goto fail; + } + + /* Previous errors - exit */ + if(*ret !=0) + goto fail; + + /* First lock check, just to be user-friendly to the operator */ + if(!rtOpts->ignore_daemon_lock) { + if(!writeLockFile(rtOpts)){ + /* check and create Lock */ + ERROR("Error: file lock failed (use -L or global:ignore_lock to ignore lock file)\n"); + *ret = 3; + goto fail; + } + /* check for potential conflicts when automatic lock files are used */ + if(!checkOtherLocks(rtOpts)) { + *ret = 3; + goto fail; + } + } + + /* Manage log files: stats, log, status and quality file */ + restartLogging(rtOpts); + + /* Allocate memory after we're done with other checks but before going into daemon */ + ptpClock = (PtpClock *) calloc(1, sizeof(PtpClock)); + if (!ptpClock) { + PERROR("Error: Failed to allocate memory for protocol engine data"); + *ret = 2; + goto fail; + } else { + DBG("allocated %d bytes for protocol engine data\n", + (int)sizeof(PtpClock)); + + + ptpClock->foreign = (ForeignMasterRecord *) + calloc(rtOpts->max_foreign_records, + sizeof(ForeignMasterRecord)); + if (!ptpClock->foreign) { + PERROR("failed to allocate memory for foreign " + "master data"); + *ret = 2; + free(ptpClock); + goto fail; + } else { + DBG("allocated %d bytes for foreign master data\n", + (int)(rtOpts->max_foreign_records * + sizeof(ForeignMasterRecord))); + } + } + + if(rtOpts->statisticsLog.logEnabled) + ptpClock->resetStatisticsLog = TRUE; + + /* Init to 0 net buffer */ + memset(ptpClock->msgIbuf, 0, PACKET_SIZE); + memset(ptpClock->msgObuf, 0, PACKET_SIZE); + + /* Init outgoing management message */ + ptpClock->outgoingManageTmp.tlv = NULL; + + + /* DAEMON */ +#ifdef PTPD_NO_DAEMON + if(!rtOpts->nonDaemon){ + rtOpts->nonDaemon=TRUE; + } +#endif + + if(!rtOpts->nonDaemon){ + /* + * fork to daemon - nochdir non-zero to preserve the working directory: + * allows relative paths to be used for log files, config files etc. + * Always redirect stdout/err to /dev/null + */ + if (daemon(1,0) == -1) { + PERROR("Failed to start as daemon"); + *ret = 3; + goto fail; + } + INFO(" Info: Now running as a daemon\n"); + /* + * Wait for the parent process to terminate, but not forever. + * On some systems this happened after we tried re-acquiring + * the lock, so the lock would fail. Hence, we wait. + */ + for (i = 0; i < 1000000; i++) { + /* Once we've been reaped by init, parent PID will be 1 */ + if(getppid() == 1) + break; + usleep(1); + } + } + + /* Second lock check, to replace the contents with our own new PID and re-acquire the advisory lock */ + if(!rtOpts->nonDaemon && !rtOpts->ignore_daemon_lock){ + /* check and create Lock */ + if(!writeLockFile(rtOpts)){ + ERROR("Error: file lock failed (use -L or global:ignore_lock to ignore lock file)\n"); + *ret = 3; + goto fail; + } + } + +#if (defined(linux) && defined(HAVE_SCHED_H)) || defined(HAVE_SYS_CPUSET_H) || defined(__QNXNTO__) + /* Try binding to a single CPU core if configured to do so */ + if(rtOpts->cpuNumber > -1) { + if(setCpuAffinity(rtOpts->cpuNumber) < 0) { + ERROR("Could not bind to CPU core %d\n", rtOpts->cpuNumber); + } else { + INFO("Successfully bound "PTPD_PROGNAME" to CPU core %d\n", rtOpts->cpuNumber); + } + } +#endif + + /* set up timers */ + if(!timerSetup(ptpClock->timers)) { + PERROR("failed to set up event timers"); + *ret = 2; + free(ptpClock); + goto fail; + } + + ptpClock->rtOpts = rtOpts; + + /* init alarms */ + initAlarms(ptpClock->alarms, ALRM_MAX, (void*)ptpClock); + configureAlarms(ptpClock->alarms, ALRM_MAX, (void*)ptpClock); + ptpClock->alarmDelay = rtOpts->alarmInitialDelay; + /* we're delaying alarm processing - disable alarms for now */ + if(ptpClock->alarmDelay) { + enableAlarms(ptpClock->alarms, ALRM_MAX, FALSE); + } + + + /* establish signal handlers */ + signal(SIGINT, catchSignals); + signal(SIGTERM, catchSignals); + signal(SIGHUP, catchSignals); + + signal(SIGUSR1, catchSignals); + signal(SIGUSR2, catchSignals); + +#if defined PTPD_SNMP + /* Start SNMP subsystem */ + if (rtOpts->snmpEnabled) + snmpInit(rtOpts, ptpClock); +#endif + + + + NOTICE(USER_DESCRIPTION" started successfully on %s using \"%s\" preset (PID %d)\n", + rtOpts->ifaceName, + (getPtpPreset(rtOpts->selectedPreset,rtOpts)).presetName, + getpid()); + ptpClock->resetStatisticsLog = TRUE; + +#ifdef PTPD_STATISTICS + + outlierFilterSetup(&ptpClock->oFilterMS); + outlierFilterSetup(&ptpClock->oFilterSM); + + ptpClock->oFilterMS.init(&ptpClock->oFilterMS,&rtOpts->oFilterMSConfig, "delayMS"); + ptpClock->oFilterSM.init(&ptpClock->oFilterSM,&rtOpts->oFilterSMConfig, "delaySM"); + + + if(rtOpts->filterMSOpts.enabled) { + ptpClock->filterMS = createDoubleMovingStatFilter(&rtOpts->filterMSOpts,"delayMS"); + } + + if(rtOpts->filterSMOpts.enabled) { + ptpClock->filterSM = createDoubleMovingStatFilter(&rtOpts->filterSMOpts, "delaySM"); + } + +#endif + +#ifdef PTPD_PCAP + ptpClock->netPath.pcapEventSock = -1; + ptpClock->netPath.pcapGeneralSock = -1; +#endif /* PTPD_PCAP */ + + ptpClock->netPath.generalSock = -1; + ptpClock->netPath.eventSock = -1; + + *ret = 0; + + return ptpClock; + +fail: + dictionary_del(&rtOpts->cliConfig); + dictionary_del(&rtOpts->candidateConfig); + dictionary_del(&rtOpts->currentConfig); + return 0; +} + +void +ntpSetup (RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + TimingService *ts = &ptpClock->ntpControl.timingService; + + + + if (rtOpts->ntpOptions.enableEngine) { + timingDomain.services[1] = ts; + strncpy(ts->id, "NTP0", TIMINGSERVICE_MAX_DESC); + ts->dataSet.priority1 = 0; + ts->dataSet.type = TIMINGSERVICE_NTP; + ts->config = &rtOpts->ntpOptions; + ts->controller = &ptpClock->ntpControl; + /* for now, NTP is considered always active, so will never go idle */ + ts->timeout = 60; + ts->updateInterval = rtOpts->ntpOptions.checkInterval; + timingDomain.serviceCount = 2; + } else { + timingDomain.serviceCount = 1; + timingDomain.services[1] = NULL; + if(timingDomain.best == ts || timingDomain.current == ts || timingDomain.preferred == ts) { + timingDomain.best = timingDomain.current = timingDomain.preferred = NULL; + } + } +} + + + + diff --git a/src/ptpd/src/dep/statistics.c b/src/ptpd/src/dep/statistics.c new file mode 100644 index 0000000..c35629f --- /dev/null +++ b/src/ptpd/src/dep/statistics.c @@ -0,0 +1,1047 @@ +/*- + * Copyright (c) 2013-2015 Wojciech Owczarek + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file statistics.c + * @authors Wojciech Owczarek + * @date Sun Jul 7 13:28:10 2013 + * This souece file contains the implementations of functions maintaining and + * computing statistical information. + */ + +#include "../ptpd.h" + +static int cmpInt32 (const void *vA, const void *vB) { + + int32_t a = *(int32_t*)vA; + int32_t b = *(int32_t*)vB; + + return ((a < b) ? -1 : (a > b) ? 1 : 0); +} + +static int cmpDouble (const void *vA, const void *vB) { + + double a = *(double*)vA; + double b = *(double*)vB; + + return ((a < b) ? -1 : (a > b) ? 1 : 0); +} + +static int cmpAbsInt32 (const void *vA, const void *vB) { + + int32_t a = abs(*(int32_t*)vA); + int32_t b = abs(*(int32_t*)vB); + + return ((a < b) ? -1 : (a > b) ? 1 : 0); +} + +static int cmpAbsDouble (const void *vA, const void *vB) { + + double a = fabs(*(double*)vA); + double b = fabs(*(double*)vB); + + return ((a < b) ? -1 : (a > b) ? 1 : 0); +} + +static int32_t median3Int(int32_t *bucket, int count) +{ + + int32_t sortedSamples[count]; + memcpy(sortedSamples, bucket, count * sizeof(int32_t)); + + switch (count) { + + case 1: + return bucket[0]; + case 2: + return (bucket[0] + bucket[1]) / 2; + case 3: + qsort(sortedSamples, count, sizeof(int32_t), cmpInt32); + return sortedSamples[1]; + default: + return 0; + + } + +} + +static double median3Double(double *bucket, int count) +{ + + double sortedSamples[count]; + memcpy(sortedSamples, bucket, count * sizeof(double)); + + switch (count) { + + case 1: + return bucket[0]; + case 2: + return (bucket[0] + bucket[1]) / 2.0; + case 3: + qsort(sortedSamples, count, sizeof(double), cmpInt32); + return sortedSamples[1]; + default: + return 0; + + } + +} + +void +resetIntPermanentMean(IntPermanentMean* container) { + + container->previous = container->mean; + container->mean = 0; + container->count = 0; + +} + +int32_t +feedIntPermanentMean(IntPermanentMean* container, int32_t sample) +{ + + container->mean += ( sample - container-> mean ) / ++container->count; + + if(container->previous) { + container->bufferedMean = (container->previous + container->mean) / 2; + } else { + container->bufferedMean = container->mean; + } + + return container->mean; + +} + +void +resetIntPermanentStdDev(IntPermanentStdDev* container) { + + resetIntPermanentMean(&container->meanContainer); + container->squareSum = 0; + container->stdDev = 0; + +} + +int32_t +feedIntPermanentStdDev(IntPermanentStdDev* container, int32_t sample) +{ + + container->squareSum += ( sample - container->meanContainer.mean) * + ( sample - feedIntPermanentMean(&container->meanContainer, sample )); + + if(container->meanContainer.count > 1) { + container->stdDev = sqrt( container->squareSum / + (container->meanContainer.count - 1) ); + } else { + container->stdDev = 0; + } + + return container->stdDev; + +} + +void +resetIntPermanentMedian(IntPermanentMedian* container) { + + memset(container, 0, sizeof(IntPermanentMedian)); + +} + +int32_t +feedIntPermanentMedian(IntPermanentMedian* container, int32_t sample) +{ + + container->bucket[container->count] = sample; + container->count++; + + container->median = median3Int(container->bucket, container->count); + + if(container->count == 3) { + container->count=1; + container->bucket[1] = container->bucket[2] = 0; + container->bucket[0] = container->median; + } + + return container->median; + +} + + +void +resetDoublePermanentMean(DoublePermanentMean* container) +{ + if(container == NULL) + return; + container->previous = container->mean; + container->mean = 0.0; + container->count = 0; + +} + +double +feedDoublePermanentMean(DoublePermanentMean* container, double sample) +{ + + container->mean += ( sample - container-> mean ) / ++container->count; + + if(container->previous) { + container->bufferedMean = (container->previous + container->mean) / 2; + } else { + container->bufferedMean = container->mean; + } + + return container->mean; + +} + +void +resetDoublePermanentStdDev(DoublePermanentStdDev* container) +{ + + if(container == NULL) + return; + resetDoublePermanentMean(&container->meanContainer); + container->squareSum = 0.0; + container->stdDev = 0.0; + +} + +double +feedDoublePermanentStdDev(DoublePermanentStdDev* container, double sample) +{ + + container->squareSum += ( sample - container->meanContainer.mean) * + ( sample - feedDoublePermanentMean(&container->meanContainer, sample )) ; + + if(container->meanContainer.count > 1) { + container->stdDev = sqrt(container->squareSum / + ((container->meanContainer.count+0.0) - 1.0)) ; + } else { + container->stdDev = 0; + } + + return container->stdDev; + +} + +void +resetDoublePermanentMedian(DoublePermanentMedian* container) +{ + + memset(container, 0, sizeof(DoublePermanentMedian)); + +} + +double +feedDoublePermanentMedian(DoublePermanentMedian* container, double sample) +{ + + container->bucket[container->count] = sample; + container->count++; + + container->median = median3Double(container->bucket, container->count); + + if(container->count == 3) { + container->count=1; + container->bucket[1] = container->bucket[2] = 0; + container->bucket[0] = container->median; + } + + return container->median; + +} + + +/* Moving statistics - up to last n samples */ + +IntMovingMean* +createIntMovingMean(int capacity) +{ + + IntMovingMean* container; + if ( !(container = calloc (1, sizeof(IntMovingMean))) ) { + return NULL; + } + + container->capacity = (capacity > STATCONTAINER_MAX_SAMPLES ) ? + STATCONTAINER_MAX_SAMPLES : capacity; + + if ( !(container->samples = calloc (container->capacity, sizeof(int32_t))) ) { + free(container); + return NULL; + } + + return container; + +} + +void +freeIntMovingMean(IntMovingMean** container) +{ + + free((*container)->samples); + free(*container); + *container = NULL; + +} + +void +resetIntMovingMean(IntMovingMean* container) +{ + + if(container == NULL) + return; + container->sum = 0; + container->mean = 0; + container->count = 0; + memset(container->samples, 0, sizeof(&container->samples)); + +} + +int32_t +feedIntMovingMean(IntMovingMean* container, int32_t sample) +{ + + if(container == NULL) return 0; + + /* sample buffer is full */ + if ( container->count == container->capacity ) { + /* keep the sum current - drop the oldest value */ + container->sum -= container->samples[0]; + /* shift the sample buffer left, dropping the oldest sample */ + memmove(container->samples, container->samples + 1, + sizeof(sample) * (container->capacity - 1) ); + /* counter will be incremented further, so we decrement it here */ + container->count--; + container->full = TRUE; + } + + container->samples[container->count++] = sample; + container->sum += sample; + container->mean = container->sum / container->count; + + return container->mean; + +} + +IntMovingStdDev* createIntMovingStdDev(int capacity) +{ + + IntMovingStdDev* container; + if ( !(container = calloc (1, sizeof(IntMovingStdDev))) ) { + return NULL; + } + + if((container->meanContainer = createIntMovingMean(capacity)) + == NULL) { + free(container); + return NULL; + } + + return container; + +} + +void +freeIntMovingStdDev(IntMovingStdDev** container) +{ + + freeIntMovingMean(&((*container)->meanContainer)); + free(*container); + *container = NULL; + +} + +void +resetIntMovingStdDev(IntMovingStdDev* container) +{ + + if(container == NULL) + return; + resetIntMovingMean(container->meanContainer); + container->squareSum = 0; + container->stdDev = 0; + +} + +int32_t +feedIntMovingStdDev(IntMovingStdDev* container, int32_t sample) +{ + + int i = 0; + + if(container == NULL) + return 0; + + feedIntMovingMean(container->meanContainer, sample); + + if (container->meanContainer->count < 2) { + container->stdDev = 0; + } else { + + container->squareSum = 0; + for(i = 0; i < container->meanContainer->count; i++) { + container->squareSum += ( container->meanContainer->samples[i] - container->meanContainer->mean ) * + ( container->meanContainer->samples[i] - container->meanContainer->mean ); + } + + container->stdDev = sqrt ( container->squareSum / + (container->meanContainer->count - 1 )); + + } + + return container->stdDev; + +} + +DoubleMovingMean* +createDoubleMovingMean(int capacity) +{ + + DoubleMovingMean* container; + if ( !(container = calloc (1, sizeof(DoubleMovingMean))) ) { + return NULL; + } + + container->capacity = (capacity > STATCONTAINER_MAX_SAMPLES ) ? + STATCONTAINER_MAX_SAMPLES : capacity; + if ( !(container->samples = calloc(container->capacity, sizeof(double))) ) { + free(container); + return NULL; + } + return container; + +} + +void +freeDoubleMovingMean(DoubleMovingMean** container) +{ + + if(*container == NULL) { + return; + } + free((*container)->samples); + free(*container); + *container = NULL; + +} + +void +resetDoubleMovingMean(DoubleMovingMean* container) +{ + + if(container == NULL) + return; + container->sum = 0; + container->mean = 0; + container->count = 0; + container->counter = 0; + memset(container->samples, 0, sizeof(&container->samples)); + +} + + +double +feedDoubleMovingMean(DoubleMovingMean* container, double sample) +{ + + if(container == NULL) + return 0; + /* sample buffer is full */ + if ( container->count == container->capacity ) { + /* keep the sum current - drop the oldest value */ + container->sum -= container->samples[0]; + /* shift the sample buffer left, dropping the oldest sample */ + memmove(container->samples, container->samples + 1, + sizeof(sample) * (container->capacity - 1) ); + /* counter will be incremented further, so we decrement it here */ + container->count--; + container->full = TRUE; + } + container->samples[container->count++] = sample; + container->sum += sample; + container->mean = container->sum / container->count; + + container->counter++; + container->counter = container->counter % container->capacity; +// INFO("c: %d\n", container->counter); + + return container->mean; + +} + +DoubleMovingStdDev* createDoubleMovingStdDev(int capacity) +{ + + DoubleMovingStdDev* container; + if ( !(container = calloc (1, sizeof(DoubleMovingStdDev))) ) { + return NULL; + } + + if((container->meanContainer = createDoubleMovingMean(capacity)) + == NULL) { + free(container); + return NULL; + } + + return container; + +} + +void +freeDoubleMovingStdDev(DoubleMovingStdDev** container) +{ + + freeDoubleMovingMean(&((*container)->meanContainer)); + free(*container); + *container = NULL; + +} + +void +resetDoubleMovingStdDev(DoubleMovingStdDev* container) +{ + + if(container == NULL) + return; + resetDoubleMovingMean(container->meanContainer); + container->squareSum = 0.0; + container->stdDev = 0.0; + +} +double +feedDoubleMovingStdDev(DoubleMovingStdDev* container, double sample) +{ + + int i = 0; + + if(container == NULL) + return 0.0; + + feedDoubleMovingMean(container->meanContainer, sample); + + if (container->meanContainer->count < 2) { + container->stdDev = 0.0; + } else { + + container->squareSum = 0.0; + for(i = 0; i < container->meanContainer->count; i++) { + container->squareSum += ( container->meanContainer->samples[i] - container->meanContainer->mean ) * + ( container->meanContainer->samples[i] - container->meanContainer->mean ); + } + + container->stdDev = sqrt ( container->squareSum / + (container->meanContainer->count - 1)); + + } + + if(container->meanContainer->counter == 0) { + container->periodicStdDev = container->stdDev; + } + + return container->stdDev; + +} + +IntMovingStatFilter* createIntMovingStatFilter(StatFilterOptions *config, const char* id) +{ + + IntMovingStatFilter* container; + + if(config->filterType > FILTER_MAXVALUE) { + return NULL; + } + + if ( !(container = calloc (1, sizeof(IntMovingStatFilter))) ) { + return NULL; + } + + if((container->meanContainer = createIntMovingMean(config->windowSize)) + == NULL) { + free(container); + return NULL; + } + + container->filterType = config->filterType; + container->windowType = config->windowType; + + if(config->windowSize < 2) container->windowType = WINDOW_SLIDING; + + strncpy(container->identifier, id, 10); + + return container; + +} + +void +freeIntMovingStatFilter(IntMovingStatFilter** container) +{ + + freeIntMovingMean(&((*container)->meanContainer)); + free(*container); + *container = NULL; + +} + +void +resetIntMovingStatFilter(IntMovingStatFilter* container) +{ + + if(container == NULL) + return; + resetIntMovingMean(container->meanContainer); + container->output = 0; + +} + +Boolean +feedIntMovingStatFilter(IntMovingStatFilter* container, int32_t sample) +{ + if(container == NULL) + return 0; + + if(container->filterType == FILTER_NONE) { + + container->output = sample; + return TRUE; + + } else { + /* cheat - the mean container is used as a general purpose sliding window */ + feedIntMovingMean(container->meanContainer, sample); + + } + + container->counter++; + container->counter = container->counter % container->meanContainer->capacity; + + switch(container->filterType) { + + case FILTER_MEAN: + + container->output = container->meanContainer->mean; + break; + + case FILTER_MEDIAN: + { + int count = container->meanContainer->count; + int32_t sortedSamples[count]; + + Boolean odd = ((count %2 ) == 1); + + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + + qsort(sortedSamples, count, sizeof(sample), cmpInt32); + + if(odd) { + + container->output = sortedSamples[(count / 2)]; + + } else { + + container->output = (sortedSamples[(count / 2) -1] + sortedSamples[(count / 2)]) / 2; + + } + + } + break; + + case FILTER_MIN: + { + int count = container->meanContainer->count; + int32_t sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpInt32); + container->output = sortedSamples[0]; + + } + break; + + case FILTER_MAX: + { + int count = container->meanContainer->count; + int32_t sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpInt32); + container->output = sortedSamples[count - 1]; + + } + break; + + case FILTER_ABSMIN: + { + int count = container->meanContainer->count; + int32_t sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpAbsInt32); + container->output = sortedSamples[0]; + + } + break; + + case FILTER_ABSMAX: + { + int count = container->meanContainer->count; + int32_t sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpAbsInt32); + container->output = sortedSamples[count - 1]; + + } + break; + default: + container->output = sample; + return TRUE; + } + + + DBGV("filter %s, Sample %d output %d\n", container->identifier, sample, container->output); + + if((container->windowType == WINDOW_INTERVAL) && (container->counter != 0)) { + return FALSE; + } + return TRUE; + +} + +DoubleMovingStatFilter* createDoubleMovingStatFilter(StatFilterOptions *config, const char* id) +{ + DoubleMovingStatFilter* container; + + if(config->filterType > FILTER_MAXVALUE) { + return NULL; + } + + if ( !(container = calloc (1, sizeof(DoubleMovingStatFilter))) ) { + return NULL; + } + + if((container->meanContainer = createDoubleMovingMean(config->windowSize)) + == NULL) { + free(container); + return NULL; + } + + container->filterType = config->filterType; + container->windowType = config->windowType; + + if(config->windowSize < 2) container->windowType = WINDOW_SLIDING; + + strncpy(container->identifier, id, 10); + + return container; + +} + +void +freeDoubleMovingStatFilter(DoubleMovingStatFilter** container) +{ + + if((container==NULL) || (*container==NULL)) { + return; + } + freeDoubleMovingMean(&((*container)->meanContainer)); + free(*container); + *container = NULL; + +} + +void +resetDoubleMovingStatFilter(DoubleMovingStatFilter* container) +{ + + if(container == NULL) + return; + resetDoubleMovingMean(container->meanContainer); + container->output = 0; + +} + +Boolean +feedDoubleMovingStatFilter(DoubleMovingStatFilter* container, double sample) +{ + + if(container == NULL) + return 0; + + if(container->filterType == FILTER_NONE) { + + container->output = sample; + return TRUE; + + } else { + /* cheat - the mean container is used as a general purpose sliding window */ + feedDoubleMovingMean(container->meanContainer, sample); + } + + container->counter++; + container->counter = container->counter % container->meanContainer->capacity; + + switch(container->filterType) { + + case FILTER_MEAN: + + container->output = container->meanContainer->mean; + break; + + case FILTER_MEDIAN: + { + int count = container->meanContainer->count; + double sortedSamples[count]; + + Boolean odd = ((count %2 ) == 1); + + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + + qsort(sortedSamples, count, sizeof(sample), cmpDouble); + + if(odd) { + + container->output = sortedSamples[(count / 2)]; + + } else { + + container->output = (sortedSamples[(count / 2) -1] + sortedSamples[(count / 2)]) / 2; + + } + + } + break; + + case FILTER_MIN: + { + int count = container->meanContainer->count; + double sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpDouble); + container->output = sortedSamples[0]; + + } + break; + + case FILTER_MAX: + { + int count = container->meanContainer->count; + double sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpDouble); + container->output = sortedSamples[count - 1]; + + } + break; + + case FILTER_ABSMIN: + { + int count = container->meanContainer->count; + double sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpAbsDouble); + container->output = sortedSamples[0]; + + } + break; + + case FILTER_ABSMAX: + { + int count = container->meanContainer->count; + double sortedSamples[count]; + memcpy(sortedSamples, container->meanContainer->samples, count * sizeof(sample)); + qsort(sortedSamples, count, sizeof(sample), cmpAbsDouble); + container->output = sortedSamples[count - 1]; + + } + break; + default: + container->output = sample; + return TRUE; + } + + DBGV("Filter %s, Sample %.09f output %.09f\n", container->identifier, sample, container->output); + + if((container->windowType == WINDOW_INTERVAL) && (container->counter != 0)) { + return FALSE; + } + return TRUE; + +} + +double getpeircesCriterion(int numObservations, int numDoubtful) { + + static const double peircesTable[60][9] = { +/* 1 - 10 samples */ + {-1, -1, -1, -1, -1, -1, -1, -1, -1}, + {-1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1.196, -1, -1, -1, -1, -1, -1, -1, -1}, + {1.383, 1.078, -1, -1, -1, -1, -1, -1, -1}, + {1.509, 1.2, -1, -1, -1, -1, -1, -1, -1}, + {1.61, 1.299, 1.099, -1, -1, -1, -1, -1, -1}, + {1.693, 1.382, 1.187, 1.022, -1, -1, -1, -1, -1}, + {1.763, 1.453, 1.261, 1.109, -1, -1, -1, -1, -1}, + {1.824, 1.515, 1.324, 1.178, 1.045, -1, -1, -1, -1}, + {1.878, 1.57, 1.38, 1.237, 1.114, -1, -1, -1, -1}, +/* 11 - 20 samples */ + {1.925, 1.619, 1.43, 1.289, 1.172, 1.059, -1, -1, -1}, + {1.969, 1.663, 1.475, 1.336, 1.221, 1.118, 1.009, -1, -1}, + {2.007, 1.704, 1.516, 1.379, 1.266, 1.167, 1.07, -1, -1}, + {2.043, 1.741, 1.554, 1.417, 1.307, 1.21, 1.12, 1.026, -1}, + {2.076, 1.775, 1.589, 1.453, 1.344, 1.249, 1.164, 1.078, -1}, + {2.106, 1.807, 1.622, 1.486, 1.378, 1.285, 1.202, 1.122, 1.039}, + {2.134, 1.836, 1.652, 1.517, 1.409, 1.318, 1.237, 1.161, 1.084}, + {2.161, 1.864, 1.68, 1.546, 1.438, 1.348, 1.268, 1.195, 1.123}, + {2.185, 1.89, 1.707, 1.573, 1.466, 1.377, 1.298, 1.226, 1.158}, + {2.209, 1.914, 1.732, 1.599, 1.492, 1.404, 1.326, 1.255, 1.19}, +/* 21 - 30 samples */ + {2.23, 1.938, 1.756, 1.623, 1.517, 1.429, 1.352, 1.282, 1.218}, + {2.251, 1.96, 1.779, 1.646, 1.54, 1.452, 1.376, 1.308, 1.245}, + {2.271, 1.981, 1.8, 1.668, 1.563, 1.475, 1.399, 1.332, 1.27}, + {2.29, 2, 1.821, 1.689, 1.584, 1.497, 1.421, 1.354, 1.293}, + {2.307, 2.019, 1.84, 1.709, 1.604, 1.517, 1.442, 1.375, 1.315}, + {2.324, 2.037, 1.859, 1.728, 1.624, 1.537, 1.462, 1.396, 1.336}, + {2.341, 2.055, 1.877, 1.746, 1.642, 1.556, 1.481, 1.415, 1.356}, + {2.356, 2.071, 1.894, 1.764, 1.66, 1.574, 1.5, 1.434, 1.375}, + {2.371, 2.088, 1.911, 1.781, 1.677, 1.591, 1.517, 1.452, 1.393}, +/* 31 - 40 samples */ + {2.385, 2.103, 1.927, 1.797, 1.694, 1.608, 1.534, 1.469, 1.411}, + {2.399, 2.118, 1.942, 1.812, 1.71, 1.624, 1.55, 1.486, 1.428}, + {2.412, 2.132, 1.957, 1.828, 1.725, 1.64, 1.567, 1.502, 1.444}, + {2.425, 2.146, 1.971, 1.842, 1.74, 1.655, 1.582, 1.517, 1.459}, + {2.438, 2.159, 1.985, 1.856, 1.754, 1.669, 1.597, 1.532, 1.475}, + {2.45, 2.172, 1.998, 1.87, 1.768, 1.683, 1.611, 1.547, 1.489}, + {2.461, 2.184, 2.011, 1.883, 1.782, 1.697, 1.624, 1.561, 1.504}, + {2.472, 2.196, 2.024, 1.896, 1.795, 1.711, 1.638, 1.574, 1.517}, + {2.483, 2.208, 2.036, 1.909, 1.807, 1.723, 1.651, 1.587, 1.531}, +/* 41 - 50 samples */ + {2.494, 2.219, 2.047, 1.921, 1.82, 1.736, 1.664, 1.6, 1.544}, + {2.504, 2.23, 2.059, 1.932, 1.832, 1.748, 1.676, 1.613, 1.556}, + {2.514, 2.241, 2.07, 1.944, 1.843, 1.76, 1.688, 1.625, 1.568}, + {2.524, 2.251, 2.081, 1.955, 1.855, 1.771, 1.699, 1.636, 1.58}, + {2.533, 2.261, 2.092, 1.966, 1.866, 1.783, 1.711, 1.648, 1.592}, + {2.542, 2.271, 2.102, 1.976, 1.876, 1.794, 1.722, 1.659, 1.603}, + {2.551, 2.281, 2.112, 1.987, 1.887, 1.804, 1.733, 1.67, 1.614}, + {2.56, 2.29, 2.122, 1.997, 1.897, 1.815, 1.743, 1.681, 1.625}, + {2.568, 2.299, 2.131, 2.006, 1.907, 1.825, 1.754, 1.691, 1.636}, + {2.577, 2.308, 2.14, 2.016, 1.917, 1.835, 1.764, 1.701, 1.646}, +/* 51 - 60 * samples */ + {2.585, 2.317, 2.149, 2.026, 1.927, 1.844, 1.773, 1.711, 1.656}, + {2.592, 2.326, 2.158, 2.035, 1.936, 1.854, 1.783, 1.721, 1.666}, + {2.6, 2.334, 2.167, 2.044, 1.945, 1.863, 1.792, 1.73, 1.675}, + {2.608, 2.342, 2.175, 2.052, 1.954, 1.872, 1.802, 1.74, 1.685}, + {2.615, 2.35, 2.184, 2.061, 1.963, 1.881, 1.811, 1.749, 1.694}, + {2.622, 2.358, 2.192, 2.069, 1.972, 1.89, 1.82, 1.758, 1.703}, + {2.629, 2.365, 2.2, 2.077, 1.98, 1.898, 1.828, 1.767, 1.711}, + {2.636, 2.373, 2.207, 2.085, 1.988, 1.907, 1.837, 1.775, 1.72}, + {2.643, 2.38, 2.215, 2.093, 1.996, 1.915, 1.845, 1.784, 1.729}, + {2.65, 2.387, 2.223, 2.101, 2.004, 1.923, 1.853, 1.792, 1.737}, + {2.656, 2.394, 2.23, 2.109, 2.012, 1.931, 1.861, 1.8, 1.745}, + {2.663, 2.401, 2.237, 2.116, 2.019, 1.939, 1.869, 1.808, 1.753}, + }; + + if ( numObservations < 1 || numObservations > 60) + return -1.0; + if ( numDoubtful < 1 || numDoubtful > 9) + return -1.0; + + return(peircesTable[numObservations - 1][numDoubtful - 1]); + +} + +Boolean isIntPeircesOutlier(IntMovingStdDev *container, int32_t sample, double threshold) { + + double maxDev; + + /* Sanity check - race condition was seen when enabling and disabling filters repeatedly */ + if(container == NULL || container->meanContainer == NULL) + return FALSE; + + maxDev = container->stdDev * getpeircesCriterion(container->meanContainer->count, 1) * threshold; + + /* + * Two cases: + * - Too early - we got a -1 from Peirce's table, + * - safeguard: std dev is zero and filter is blocking + * everything, hus, we let the sample through + */ + if (maxDev <= 0.0 ) return FALSE; + + if(fabs((double)(sample - container->meanContainer->mean)) > maxDev) { + DBGV("Peirce %s outlier: val: %d, cnt: %d, mxd: %.09f (%.03f * dev * %.03f), dev: %.09f, mea: %.09f, dif: %.09f\n", container->identifier, + sample, container->meanContainer->count, maxDev, getpeircesCriterion(container->meanContainer->count, 1), threshold, + container->stdDev, + container->meanContainer->mean, fabs(sample - container->meanContainer->mean)); + return TRUE; + } + + return FALSE; + +} + +Boolean isDoublePeircesOutlier(DoubleMovingStdDev *container, double sample, double threshold) { + + double maxDev; + + /* Sanity check - race condition was seen when enabling and disabling filters repeatedly */ + if(container == NULL || container->meanContainer == NULL) + return FALSE; + + maxDev = container->stdDev * getpeircesCriterion(container->meanContainer->count, 1) * threshold; + + /* + * Two cases: + * - Too early - we got a -1 from Peirce's table, + * - safeguard: std dev is zero and filter is blocking + * everything, thus, we let the sample through + */ + if (maxDev <= 0.0 ) { + return FALSE; + } + + if(fabs((double)(sample - container->meanContainer->mean)) > maxDev) { + DBG("Peirce %s outlier: val: %.09f, cnt: %d, mxd: %.09f (%.03f * dev * %.03f), dev: %.09f, mea: %.09f, dif: %.09f\n", container->identifier, + sample, container->meanContainer->count, maxDev, getpeircesCriterion(container->meanContainer->count, 1), threshold, + container->stdDev, + container->meanContainer->mean, fabs(sample - container->meanContainer->mean)); + return TRUE; + } + + return FALSE; + +} + +void +clearPtpEngineSlaveStats(PtpEngineSlaveStats* stats) +{ + memset(stats, 0, sizeof(*stats)); +} + +void +resetPtpEngineSlaveStats(PtpEngineSlaveStats* stats) { + + resetDoublePermanentStdDev(&stats->ofmStats); + resetDoublePermanentStdDev(&stats->mpdStats); + resetDoublePermanentMedian(&stats->ofmMedianContainer); + resetDoublePermanentMedian(&stats->mpdMedianContainer); + stats->ofmStatsUpdated = FALSE; + stats->mpdStatsUpdated = FALSE; +} diff --git a/src/ptpd/src/dep/statistics.h b/src/ptpd/src/dep/statistics.h new file mode 100644 index 0000000..8383db9 --- /dev/null +++ b/src/ptpd/src/dep/statistics.h @@ -0,0 +1,230 @@ +/** + * @file statistics.h + * @authors Wojciech Owczarek + * @date Sun Jul 7 13:28:10 2013 + * + * This header file contains the definitions of structures and functions + * implementing standard statistical measures - means and standard deviations, + * both complete (from t0 to tnow) and moving (from t0+n*windowsize to t0+(n+1)*windowsize) + */ + +#ifndef STATISTICS_H_ +#define STATISTICS_H_ + +#define STATCONTAINER_MAX_SAMPLES 60 + +/* "Permanent" i.e. non-moving statistics containers - useful for long term measurement */ + +typedef struct { + + int32_t mean; + int32_t previous; + int32_t bufferedMean; + int32_t count; + +} IntPermanentMean; + +typedef struct { + + double mean; + double previous; + double bufferedMean; + double count; + +} DoublePermanentMean; + +typedef struct { + + IntPermanentMean meanContainer; + int32_t squareSum; + int32_t stdDev; + +} IntPermanentStdDev; + +typedef struct { + + DoublePermanentMean meanContainer; + double squareSum; + double stdDev; + +} DoublePermanentStdDev; + +typedef struct { + int32_t median; + int32_t bucket[3]; + uint8_t count; +} IntPermanentMedian; + +typedef struct { + double median; + double bucket[3]; + uint8_t count; +} DoublePermanentMedian; + +void resetIntPermanentMean(IntPermanentMean* container); +int32_t feedIntPermanentMean(IntPermanentMean* container, int32_t sample); +void resetIntPermanentStdDev(IntPermanentStdDev* container); +int32_t feedIntPermanentStdDev(IntPermanentStdDev* container, int32_t sample); +void resetIntPermanentMedian(IntPermanentMedian* container); +int32_t feedIntPermanentMedian(IntPermanentMedian* container, int32_t sample); + +void resetDoublePermanentMean(DoublePermanentMean* container); +double feedDoublePermanentMean(DoublePermanentMean* container, double sample); +void resetDoublePermanentStdDev(DoublePermanentStdDev* container); +double feedDoublePermanentStdDev(DoublePermanentStdDev* container, double sample); +void resetDoublePermanentMedian(DoublePermanentMedian* container); +double feedDoublePermanentMedian(DoublePermanentMedian* container, double sample); + +/* Moving statistics - up to last n samples */ + +typedef struct { + + int32_t mean; + int32_t sum; + int32_t* samples; + Boolean full; + int count; + int capacity; + +} IntMovingMean; + +typedef struct { + + double mean; + double sum; + double* samples; + Boolean full; + int count; + int counter; + int capacity; + +} DoubleMovingMean; + +typedef struct { + + IntMovingMean* meanContainer; + int32_t squareSum; + int32_t stdDev; + char* identifier[10]; + +} IntMovingStdDev; + +typedef struct { + + DoubleMovingMean* meanContainer; + double squareSum; + double stdDev; + double periodicStdDev; + char identifier[10]; + +} DoubleMovingStdDev; + +typedef struct { + + IntMovingMean* meanContainer; + int32_t output; + int32_t* sortedSamples; + char identifier[10]; + int counter; + uint8_t filterType; + uint8_t windowType; + +} IntMovingStatFilter; + +typedef struct { + + DoubleMovingMean* meanContainer; + double output; + double* sortedSamples; + char identifier[10]; + int counter; + uint8_t filterType; + uint8_t windowType; + +} DoubleMovingStatFilter; + +typedef struct { + + Boolean enabled; + uint8_t filterType; + int windowSize; + uint8_t windowType; + +} StatFilterOptions; + +IntMovingMean* createIntMovingMean(int capacity); +void freeIntMovingMean(IntMovingMean** container); +void resetIntMovingMean(IntMovingMean* container); +int32_t feedIntMovingMean(IntMovingMean* container, int32_t sample); + +IntMovingStdDev* createIntMovingStdDev(int capacity); +void freeIntMovingStdDev(IntMovingStdDev** container); +void resetIntMovingStdDev(IntMovingStdDev* container); +int32_t feedIntMovingStdDev(IntMovingStdDev* container, int32_t sample); + +DoubleMovingMean* createDoubleMovingMean(int capacity); +void freeDoubleMovingMean(DoubleMovingMean** container); +void resetDoubleMovingMean(DoubleMovingMean* container); +double feedDoubleMovingMean(DoubleMovingMean* container, double sample); + +DoubleMovingStdDev* createDoubleMovingStdDev(int capacity); +void freeDoubleMovingStdDev(DoubleMovingStdDev** container); +void resetDoubleMovingStdDev(DoubleMovingStdDev* container); +double feedDoubleMovingStdDev(DoubleMovingStdDev* container, double sample); + +IntMovingStatFilter* createIntMovingStatFilter(StatFilterOptions* config, const char* id); +void freeIntMovingStatFilter(IntMovingStatFilter** container); +void resetIntMovingStatFilter(IntMovingStatFilter* container); +Boolean feedIntMovingStatFilter(IntMovingStatFilter* container, int32_t sample); + +DoubleMovingStatFilter* createDoubleMovingStatFilter(StatFilterOptions* config, const char* id); +void freeDoubleMovingStatFilter(DoubleMovingStatFilter** container); +void resetDoubleMovingStatFilter(DoubleMovingStatFilter* container); +Boolean feedDoubleMovingStatFilter(DoubleMovingStatFilter* container, double sample); + +void intStatsTest(int32_t sample); +void doubleStatsTest(double sample); + +double getPeircesCriterion(int numObservations, int numDoubtful); + +Boolean isIntPeircesOutlier(IntMovingStdDev *container, int32_t sample, double threshold); +Boolean isDoublePeircesOutlier(DoubleMovingStdDev *container, double sample, double threshold); + +/** + * \struct PtpEngineSlaveStats + * \brief Ptpd clock statistics per port + */ +typedef struct +{ + Boolean statsCalculated; + Boolean ofmStatsUpdated; + Boolean mpdStatsUpdated; + double ofmMean; + double ofmStdDev; + double ofmMedian; + double ofmMin; + double ofmMinFinal; + double ofmMax; + double ofmMaxFinal; + double mpdMean; + double mpdStdDev; + double mpdMedian; + double mpdMin; + double mpdMinFinal; + double mpdMax; + double mpdMaxFinal; + Boolean mpdIsStable; + double mpdStabilityThreshold; + int mpdStabilityPeriod; + DoublePermanentStdDev ofmStats; + DoublePermanentStdDev mpdStats; + DoublePermanentMedian ofmMedianContainer; + DoublePermanentMedian mpdMedianContainer; +} PtpEngineSlaveStats; + +void clearPtpEngineSlaveStats(PtpEngineSlaveStats* stats); +void resetPtpEngineSlaveStats(PtpEngineSlaveStats* stats); + +#endif /*STATISTICS_H_*/ + + diff --git a/src/ptpd/src/dep/sys.c b/src/ptpd/src/dep/sys.c new file mode 100644 index 0000000..64617cf --- /dev/null +++ b/src/ptpd/src/dep/sys.c @@ -0,0 +1,2675 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ +/*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sys.c + * @date Tue Jul 20 16:19:46 2010 + * + * @brief Code to call kernel time routines and also display server statistics. + * + * + */ + +#include "../ptpd.h" + +#ifdef HAVE_NETINET_ETHER_H +# include +#endif + +#ifdef HAVE_NET_ETHERNET_H +# include +#endif + +#ifdef HAVE_NET_IF_H +# include +#endif + +#ifdef HAVE_NET_IF_ETHER_H +# include +#endif + +/* only C99 has the round function built-in */ +double round (double __x); + +static int closeLog(LogFileHandler* handler); + +#ifdef __QNXNTO__ +typedef struct { + _uint64 counter; /* iteration counter */ + _uint64 prev_tsc; /* previous clock cycles */ + _uint64 last_clock; /* clock reading at last timer interrupt */ + _uint64 cps; /* cycles per second */ + _uint64 prev_delta; /* previous clock cycle delta */ + _uint64 cur_delta; /* last clock cycle delta */ + _uint64 filtered_delta; /* filtered delta */ + double ns_per_tick; /* nanoseconds per cycle */ +} TimerIntData; + +/* do not access directly! tied to clock interrupt! */ +volatile static TimerIntData tData; /* using 'volatile' because the object is used in interrupt context */ +static Boolean tDataUpdated = FALSE; + +#endif /* __QNXNTO__ */ + +/* + returns a static char * for the representation of time, for debug purposes + DO NOT call this twice in the same printf! +*/ +char *dump_TimeInternal(const TimeInternal * p) +{ + static char buf[100]; + + snprint_TimeInternal(buf, 100, p); + return buf; +} + + +/* + displays 2 timestamps and their strings in sequence, and the difference between then + DO NOT call this twice in the same printf! +*/ +char *dump_TimeInternal2(const char *st1, const TimeInternal * p1, const char *st2, const TimeInternal * p2) +{ + static char buf[BUF_SIZE]; + int n = 0; + + /* display Timestamps */ + if (st1) { + n += snprintf(buf + n, BUF_SIZE - n, "%s ", st1); + } + n += snprint_TimeInternal(buf + n, BUF_SIZE - n, p1); + n += snprintf(buf + n, BUF_SIZE - n, " "); + + if (st2) { + n += snprintf(buf + n, BUF_SIZE - n, "%s ", st2); + } + n += snprint_TimeInternal(buf + n, BUF_SIZE - n, p2); + n += snprintf(buf + n, BUF_SIZE - n, " "); + + /* display difference */ + TimeInternal r; + subTime(&r, p1, p2); + n += snprintf(buf + n, BUF_SIZE - n, " (diff: "); + n += snprint_TimeInternal(buf + n, BUF_SIZE - n, &r); + n += snprintf(buf + n, BUF_SIZE - n, ") "); + + return buf; +} + + + +int +snprint_TimeInternal(char *s, int max_len, const TimeInternal * p) +{ + int len = 0; + + /* always print either a space, or the leading "-". This makes the stat files columns-aligned */ + len += snprintf(&s[len], max_len - len, "%c", + isTimeInternalNegative(p)? '-':' '); + + len += snprintf(&s[len], max_len - len, "%d.%09d", + abs(p->seconds), abs(p->nanoseconds)); + + return len; +} + + +/* debug aid: convert a time variable into a static char */ +char *time2st(const TimeInternal * p) +{ + static char buf[1000]; + + snprint_TimeInternal(buf, sizeof(buf), p); + return buf; +} + + + +void DBG_time(const char *name, const TimeInternal p) +{ + DBG(" %s: %s\n", name, time2st(&p)); + +} + + +char * +translatePortState(PtpClock *ptpClock) +{ + char *s; + switch(ptpClock->portDS.portState) { + case PTP_INITIALIZING: s = "init"; break; + case PTP_FAULTY: s = "flt"; break; + case PTP_LISTENING: + /* seperate init-reset from real resets */ + if(ptpClock->resetCount == 1){ + s = "lstn_init"; + } else { + s = "lstn_reset"; + } + break; + case PTP_PASSIVE: s = "pass"; break; + case PTP_UNCALIBRATED: s = "uncl"; break; + case PTP_SLAVE: s = "slv"; break; + case PTP_PRE_MASTER: s = "pmst"; break; + case PTP_MASTER: s = "mst"; break; + case PTP_DISABLED: s = "dsbl"; break; + default: s = "?"; break; + } + return s; +} + + +int +snprint_ClockIdentity(char *s, int max_len, const ClockIdentity id) +{ + int len = 0; + int i; + + for (i = 0; ;) { + len += snprintf(&s[len], max_len - len, "%02x", (unsigned char) id[i]); + + if (++i >= CLOCK_IDENTITY_LENGTH) + break; + } + + return len; +} + + +/* show the mac address in an easy way */ +int +snprint_ClockIdentity_mac(char *s, int max_len, const ClockIdentity id) +{ + int len = 0; + int i; + + for (i = 0; ;) { + /* skip bytes 3 and 4 */ + if(!((i==3) || (i==4))){ + len += snprintf(&s[len], max_len - len, "%02x", (unsigned char) id[i]); + + if (++i >= CLOCK_IDENTITY_LENGTH) + break; + + /* print a separator after each byte except the last one */ + len += snprintf(&s[len], max_len - len, "%s", ":"); + } else { + + i++; + } + } + + return len; +} + + +/* + * wrapper that caches the latest value of ether_ntohost + * this function will NOT check the last accces time of /etc/ethers, + * so it only have different output on a failover or at restart + * + */ +int ether_ntohost_cache(char *hostname, struct ether_addr *addr) +{ + static int valid = 0; + static struct ether_addr prev_addr; + static char buf[BUF_SIZE]; + +#ifdef HAVE_STRUCT_ETHER_ADDR_OCTET + if (memcmp(addr->octet, &prev_addr, + sizeof(struct ether_addr )) != 0) { + valid = 0; + } +#else + if (memcmp(addr->ether_addr_octet, &prev_addr, + sizeof(struct ether_addr )) != 0) { + valid = 0; + } +#endif + if (!valid) { + if(ether_ntohost(buf, addr)){ + snprintf(buf, BUF_SIZE,"%s", "unknown"); + } + + /* clean possible commas from the string */ + while (strchr(buf, ',') != NULL) { + *(strchr(buf, ',')) = '_'; + } + + prev_addr = *addr; + } + + valid = 1; + strncpy(hostname, buf, 100); + return 0; +} + + +/* Show the hostname configured in /etc/ethers */ +int +snprint_ClockIdentity_ntohost(char *s, int max_len, const ClockIdentity id) +{ + int len = 0; + int i,j; + char buf[100]; + struct ether_addr e; + + /* extract mac address */ + for (i = 0, j = 0; i< CLOCK_IDENTITY_LENGTH ; i++ ){ + /* skip bytes 3 and 4 */ + if(!((i==3) || (i==4))){ +#ifdef HAVE_STRUCT_ETHER_ADDR_OCTET + e.octet[j] = (uint8_t) id[i]; +#else + e.ether_addr_octet[j] = (uint8_t) id[i]; +#endif + j++; + } + } + + /* convert and print hostname */ + ether_ntohost_cache(buf, &e); + len += snprintf(&s[len], max_len - len, "(%s)", buf); + + return len; +} + + +int +snprint_PortIdentity(char *s, int max_len, const PortIdentity *id) +{ + int len = 0; + +#ifdef PRINT_MAC_ADDRESSES + len += snprint_ClockIdentity_mac(&s[len], max_len - len, id->clockIdentity); +#else + len += snprint_ClockIdentity(&s[len], max_len - len, id->clockIdentity); +#endif + + len += snprint_ClockIdentity_ntohost(&s[len], max_len - len, id->clockIdentity); + + len += snprintf(&s[len], max_len - len, "/%d", (unsigned) id->portNumber); + return len; +} + +/* Write a formatted string to file pointer */ +int writeMessage(FILE* destination, uint32_t *lastHash, int priority, const char * format, va_list ap) { + extern RunTimeOpts rtOpts; + extern Boolean startupInProgress; + + char time_str[MAXTIMESTR]; + struct timeval now; + + +#ifndef RUNTIME_DEBUG + char buf[PATH_MAX]; + uint32_t hash; + va_list ap1; +#endif /* RUNTIME_DEBUG */ + + extern char *translatePortState(PtpClock *ptpClock); + extern PtpClock *G_ptpClock; + + + + if(destination == NULL) + return -1; + + /* If we're starting up as daemon, only print <= WARN */ + if ((destination == stderr) && + !rtOpts.nonDaemon && + startupInProgress && + (priority > LOG_WARNING)) { + return 1; + } + + + +#ifndef RUNTIME_DEBUG + /* check if this message produces the same hash as last */ + memset(buf, 0, sizeof(buf)); + va_copy(ap1, ap); + vsnprintf(buf, PATH_MAX, format, ap1); + hash = fnvHash(buf, sizeof(buf), 0); + if(lastHash != NULL) { + if(format[0] != '\n' && lastHash != NULL) { + /* last message was the same - don't print the next one */ + if( (lastHash != 0) && (hash == *lastHash)) { + return 0; + } + } + *lastHash = hash; + } +#endif /* RUNTIME_DEBUG */ + + /* Print timestamps and prefixes only if we're running in foreground or logging to file*/ + if( rtOpts.nonDaemon || destination != stderr) { + /* + * select debug tagged with timestamps. This will slow down PTP itself if you send a lot of messages! + * it also can cause problems in nested debug statements (which are solved by turning the signal + * handling synchronous, and not calling this function inside asycnhronous signal processing) + */ + gettimeofday(&now, 0); + strftime(time_str, MAXTIMESTR, "%F %X", localtime((time_t*)&now.tv_sec)); + fprintf(destination, "%s.%06d ", time_str, (int)now.tv_usec); + fprintf(destination, PTPD_PROGNAME"[%d].%s (%-9s ", + (int)getpid(), startupInProgress ? "startup" : rtOpts.ifaceName, + priority == LOG_EMERG ? "emergency)" : + priority == LOG_ALERT ? "alert)" : + priority == LOG_CRIT ? "critical)" : + priority == LOG_ERR ? "error)" : + priority == LOG_WARNING ? "warning)" : + priority == LOG_NOTICE ? "notice)" : + priority == LOG_INFO ? "info)" : + priority == LOG_DEBUG ? "debug1)" : + priority == LOG_DEBUG2 ? "debug2)" : + priority == LOG_DEBUGV ? "debug3)" : + "unk)"); + + + fprintf(destination, " (%s) ", G_ptpClock ? + translatePortState(G_ptpClock) : "___"); + } + + return vfprintf(destination, format, ap); +} + +void +updateLogSize(LogFileHandler* handler) +{ + + struct stat st; + if(handler->logFP == NULL) + return; + if (fstat(fileno(handler->logFP), &st) != -1) { + handler->fileSize = st.st_size; + } else { +#ifdef RUNTIME_DEBUG +/* 2.3.1: do not print to stderr or log file */ +// fprintf(stderr, "fstat on %s file failed!\n", handler->logID); +#endif /* RUNTIME_DEBUG */ + } + +} + + +/* + * Prints a message, randing from critical to debug. + * This either prints the message to syslog, or with timestamp+state to stderr + */ +void +logMessage(int priority, const char * format, ...) +{ + extern RunTimeOpts rtOpts; + extern Boolean startupInProgress; + va_list ap , ap1; + + va_start(ap, format); + va_copy(ap1, ap); + +#ifdef RUNTIME_DEBUG + if ((priority >= LOG_DEBUG) && (priority > rtOpts.debug_level)) { + goto end; + } +#endif + + /* log level filter */ + if(priority > rtOpts.logLevel) { + goto end; + } + /* If we're using a log file and the message has been written OK, we're done*/ + if(rtOpts.eventLog.logEnabled && rtOpts.eventLog.logFP != NULL) { + if(writeMessage(rtOpts.eventLog.logFP, &rtOpts.eventLog.lastHash, priority, format, ap) > 0) { + maintainLogSize(&rtOpts.eventLog); + if(!startupInProgress) { + goto end; + } + else { + rtOpts.eventLog.lastHash = 0; + goto std_err; + } + } + } + + /* + * Otherwise we try syslog - if we're here, we didn't write to log file. + * If we're running in background and we're starting up, also log first + * messages to syslog to at least leave a trace. + */ + if (rtOpts.useSysLog || + (!rtOpts.nonDaemon && startupInProgress)) { + static Boolean syslogOpened; +#ifdef RUNTIME_DEBUG + /* + * Syslog only has 8 message levels (3 bits) + * important: messages will only appear if "*.debug /var/log/debug" is on /etc/rsyslog.conf + */ + if(priority > LOG_DEBUG){ + priority = LOG_DEBUG; + } +#endif + + if (!syslogOpened) { + openlog(PTPD_PROGNAME, LOG_PID, LOG_DAEMON); + syslogOpened = TRUE; + } + vsyslog(priority, format, ap); + if (!startupInProgress) { + goto end; + } + else { + rtOpts.eventLog.lastHash = 0; + goto std_err; + } + } +std_err: + + /* Either all else failed or we're running in foreground - or we also log to stderr */ + writeMessage(stderr, &rtOpts.eventLog.lastHash, priority, format, ap1); + +end: + va_end(ap1); + va_end(ap); + return; +} + + +/* Restart a file log target based on its settings */ +int +restartLog(LogFileHandler* handler, Boolean quiet) +{ + + /* The FP is open - close it */ + if(handler->logFP != NULL) { + handler->lastHash=0; + fclose(handler->logFP); + /* + * fclose doesn't do this at least on Linux - changes the underlying FD to -1, + * but not the FP to NULL - with this we can tell if the FP is closed + */ + handler->logFP=NULL; + /* If we're not logging to file (any more), call it quits */ + if (!handler->logEnabled) { + if(!quiet) INFO("Logging to %s file disabled. Closing file.\n", handler->logID); + if(handler->unlinkOnClose) + unlink(handler->logPath); + return 1; + } + } + /* FP is not open and we're not logging */ + if (!handler->logEnabled) + return 1; + + /* Open the file */ + if ( (handler->logFP = fopen(handler->logPath, handler->openMode)) == NULL) { + if(!quiet) PERROR("Could not open %s file", handler->logID); + } else { + if(handler->truncateOnReopen) { + if(!ftruncate(fileno(handler->logFP), 0)) { + if(!quiet) INFO("Truncated %s file\n", handler->logID); + } else + DBG("Could not truncate % file: %s\n", handler->logID, handler->logPath); + } + /* \n flushes output for us, no need for fflush() - if you want something different, set it later */ + setlinebuf(handler->logFP); + + } + return (handler->logFP != NULL); +} + +/* Close a file log target */ +static int +closeLog(LogFileHandler* handler) +{ + if(handler->logFP != NULL) { + fclose(handler->logFP); + handler->logFP=NULL; + return 1; + } + + return 0; +} + + +/* Return TRUE only if the log file had to be rotated / truncated - FALSE does not mean error */ +/* Mini-logrotate: truncate file if exceeds preset size, also rotate up to n number of files if configured */ +Boolean +maintainLogSize(LogFileHandler* handler) +{ + + if(handler->maxSize) { + if(handler->logFP == NULL) + return FALSE; + updateLogSize(handler); +#ifdef RUNTIME_DEBUG +/* 2.3.1: do not print to stderr or log file */ +// fprintf(stderr, "%s logsize: %d\n", handler->logID, handler->fileSize); +#endif /* RUNTIME_DEBUG */ + if(handler->fileSize > (handler->maxSize * 1024)) { + + /* Rotate the log file */ + if (handler->maxFiles) { + int i = 0; + int logFileNumber = 0; + time_t maxMtime = 0; + struct stat st; + char fname[PATH_MAX]; + /* Find the last modified file of the series */ + while(++i <= handler->maxFiles) { + memset(fname, 0, PATH_MAX); + snprintf(fname, PATH_MAX,"%s.%d", handler->logPath, i); + if((stat(fname,&st) != -1) && S_ISREG(st.st_mode)) { + if(st.st_mtime > maxMtime) { + maxMtime = st.st_mtime; + logFileNumber = i; + } + } + } + /* Use next file in line or first one if rolled over */ + if(++logFileNumber > handler->maxFiles) + logFileNumber = 1; + memset(fname, 0, PATH_MAX); + snprintf(fname, PATH_MAX,"%s.%d", handler->logPath, logFileNumber); + /* Move current file to new location */ + rename(handler->logPath, fname); + /* Reopen to reactivate the original path */ + if(restartLog(handler,TRUE)) { + INFO("Rotating %s file - size above %dkB\n", + handler->logID, handler->maxSize); + } else { + DBG("Could not rotate %s file\n", handler->logPath); + } + return TRUE; + /* Just truncate - maxSize given but no maxFiles */ + } else { + + if(!ftruncate(fileno(handler->logFP),0)) { + INFO("Truncating %s file - size above %dkB\n", + handler->logID, handler->maxSize); + } else { +#ifdef RUNTIME_DEBUG +/* 2.3.1: do not print to stderr or log file */ +// fprintf(stderr, "Could not truncate %s file\n", handler->logPath); +#endif + } + return TRUE; + } + } + } + + return FALSE; + +} + +void +restartLogging(RunTimeOpts* rtOpts) +{ + + if(!restartLog(&rtOpts->statisticsLog, TRUE)) + NOTIFY("Failed logging to %s file\n", rtOpts->statisticsLog.logID); + + if(!restartLog(&rtOpts->recordLog, TRUE)) + NOTIFY("Failed logging to %s file\n", rtOpts->recordLog.logID); + + if(!restartLog(&rtOpts->eventLog, TRUE)) + NOTIFY("Failed logging to %s file\n", rtOpts->eventLog.logID); + + if(!restartLog(&rtOpts->statusLog, TRUE)) + NOTIFY("Failed logging to %s file\n", rtOpts->statusLog.logID); + +} + +void +stopLogging(RunTimeOpts* rtOpts) +{ + closeLog(&rtOpts->statisticsLog); + closeLog(&rtOpts->recordLog); + closeLog(&rtOpts->eventLog); + closeLog(&rtOpts->statusLog); +} + +void +logStatistics(PtpClock * ptpClock) +{ + extern RunTimeOpts rtOpts; + static int errorMsg = 0; + static char sbuf[SCREEN_BUFSZ * 2]; + int len = 0; + TimeInternal now; + time_t time_s; + FILE* destination; + static TimeInternal prev_now_sync, prev_now_delay; + char time_str[MAXTIMESTR]; + + if (!rtOpts.logStatistics) { + return; + } + + if(rtOpts.statisticsLog.logEnabled && rtOpts.statisticsLog.logFP != NULL) + destination = rtOpts.statisticsLog.logFP; + else + destination = stdout; + + if (ptpClock->resetStatisticsLog) { + ptpClock->resetStatisticsLog = FALSE; + fprintf(destination,"# %s, State, Clock ID, One Way Delay, " + "Offset From Master, Slave to Master, " + "Master to Slave, Observed Drift, Last packet Received, Sequence ID" +#ifdef PTPD_STATISTICS + ", One Way Delay Mean, One Way Delay Std Dev, Offset From Master Mean, Offset From Master Std Dev, Observed Drift Mean, Observed Drift Std Dev, raw delayMS, raw delaySM" +#endif + "\n", (rtOpts.statisticsTimestamp == TIMESTAMP_BOTH) ? "Timestamp, Unix timestamp" : "Timestamp"); + } + + memset(sbuf, 0, sizeof(sbuf)); + + getTime(&now); + + /* + * print one log entry per X seconds for Sync and DelayResp messages, to reduce disk usage. + */ + + if ((ptpClock->portDS.portState == PTP_SLAVE) && (rtOpts.statisticsLogInterval)) { + + switch(ptpClock->char_last_msg) { + case 'S': + if((now.seconds - prev_now_sync.seconds) < rtOpts.statisticsLogInterval){ + DBGV("Suppressed Sync statistics log entry - statisticsLogInterval configured\n"); + return; + } + prev_now_sync = now; + break; + case 'D': + case 'P': + if((now.seconds - prev_now_delay.seconds) < rtOpts.statisticsLogInterval){ + DBGV("Suppressed Sync statistics log entry - statisticsLogInterval configured\n"); + return; + } + prev_now_delay = now; + default: + break; + } + } + + time_s = now.seconds; + + /* output date-time timestamp if configured */ + if (rtOpts.statisticsTimestamp == TIMESTAMP_DATETIME || + rtOpts.statisticsTimestamp == TIMESTAMP_BOTH) { + strftime(time_str, MAXTIMESTR, "%Y-%m-%d %X", localtime(&time_s)); + len += snprintf(sbuf + len, sizeof(sbuf) - len, "%s.%06d, %s, ", + time_str, (int)now.nanoseconds/1000, /* Timestamp */ + translatePortState(ptpClock)); /* State */ + } + + /* output unix timestamp s.ns if configured */ + if (rtOpts.statisticsTimestamp == TIMESTAMP_UNIX || + rtOpts.statisticsTimestamp == TIMESTAMP_BOTH) { + len += snprintf(sbuf + len, sizeof(sbuf) - len, "%d.%06d, %s,", + now.seconds, now.nanoseconds, /* Timestamp */ + translatePortState(ptpClock)); /* State */ + } + + if (ptpClock->portDS.portState == PTP_SLAVE) { + len += snprint_PortIdentity(sbuf + len, sizeof(sbuf) - len, + &ptpClock->parentDS.parentPortIdentity); /* Clock ID */ + + /* + * if grandmaster ID differs from parent port ID then + * also print GM ID + */ + if (memcmp(ptpClock->parentDS.grandmasterIdentity, + ptpClock->parentDS.parentPortIdentity.clockIdentity, + CLOCK_IDENTITY_LENGTH)) { + len += snprint_ClockIdentity(sbuf + len, + sizeof(sbuf) - len, + ptpClock->parentDS.grandmasterIdentity); + } + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", "); + + if(rtOpts.delayMechanism == E2E) { + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &ptpClock->currentDS.meanPathDelay); + } else { + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &ptpClock->portDS.peerMeanPathDelay); + } + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", "); + + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &ptpClock->currentDS.offsetFromMaster); + + /* print MS and SM with sign */ + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", "); + + if(rtOpts.delayMechanism == E2E) { + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &(ptpClock->delaySM)); + } else { + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &(ptpClock->pdelaySM)); + } + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", "); + + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &(ptpClock->delayMS)); + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", %.09f, %c, %05d", + ptpClock->servo.observedDrift, + ptpClock->char_last_msg, + ptpClock->msgTmpHeader.sequenceId); + +#ifdef PTPD_STATISTICS + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", %.09f, %.00f, %.09f, %.00f", + ptpClock->slaveStats.mpdMean, + ptpClock->slaveStats.mpdStdDev * 1E9, + ptpClock->slaveStats.ofmMean, + ptpClock->slaveStats.ofmStdDev * 1E9); + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", %.0f, %.0f, ", + ptpClock->servo.driftMean, + ptpClock->servo.driftStdDev); + + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &(ptpClock->rawDelayMS)); + + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", "); + + len += snprint_TimeInternal(sbuf + len, sizeof(sbuf) - len, + &(ptpClock->rawDelaySM)); + +#endif /* PTPD_STATISTICS */ + + } else { + if ((ptpClock->portDS.portState == PTP_MASTER) || (ptpClock->portDS.portState == PTP_PASSIVE)) { + + len += snprint_PortIdentity(sbuf + len, sizeof(sbuf) - len, + &ptpClock->parentDS.parentPortIdentity); + + //len += snprintf(sbuf + len, sizeof(sbuf) - len, ")"); + } + + /* show the current reset number on the log */ + if (ptpClock->portDS.portState == PTP_LISTENING) { + len += snprintf(sbuf + len, + sizeof(sbuf) - len, + " %d ", ptpClock->resetCount); + } + } + + /* add final \n in normal status lines */ + len += snprintf(sbuf + len, sizeof(sbuf) - len, "\n"); + +#if 0 /* NOTE: Do we want this? */ + if (rtOpts.nonDaemon) { + /* in -C mode, adding an extra \n makes stats more clear intermixed with debug comments */ + len += snprintf(sbuf + len, sizeof(sbuf) - len, "\n"); + } +#endif + + /* fprintf may get interrupted by a signal - silently retry once */ + if (fprintf(destination, "%s", sbuf) < len) { + if (fprintf(destination, "%s", sbuf) < len) { + if(!errorMsg) { + PERROR("Error while writing statistics"); + } + errorMsg = TRUE; + } + } + + if(destination == rtOpts.statisticsLog.logFP) { + if (maintainLogSize(&rtOpts.statisticsLog)) + ptpClock->resetStatisticsLog = TRUE; + } + +} + +/* periodic status update */ +void +periodicUpdate(const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + char tmpBuf[200]; + char masterIdBuf[150]; + int len = 0; + + memset(tmpBuf, 0, sizeof(tmpBuf)); + memset(masterIdBuf, 0, sizeof(masterIdBuf)); + + TimeInternal *mpd = &ptpClock->currentDS.meanPathDelay; + + len += snprint_PortIdentity(masterIdBuf + len, sizeof(masterIdBuf) - len, + &ptpClock->parentDS.parentPortIdentity); + if(ptpClock->bestMaster && ptpClock->bestMaster->sourceAddr) { + char strAddr[MAXHOSTNAMELEN]; + struct in_addr tmpAddr; + tmpAddr.s_addr = ptpClock->bestMaster->sourceAddr; + inet_ntop(AF_INET, (struct sockaddr_in *)(&tmpAddr), strAddr, MAXHOSTNAMELEN); + len += snprintf(masterIdBuf + len, sizeof(masterIdBuf) - len, " (IPv4:%s)",strAddr); + } + + if(ptpClock->portDS.delayMechanism == P2P) { + mpd = &ptpClock->portDS.peerMeanPathDelay; + } + + if(ptpClock->portDS.portState == PTP_SLAVE) { + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), &ptpClock->currentDS.offsetFromMaster); +#ifdef PTPD_STATISTICS + if(ptpClock->slaveStats.statsCalculated) { + INFO("Status update: state %s, best master %s, ofm %s s, ofm_mean % .09f s, ofm_dev % .09f s\n", + portState_getName(ptpClock->portDS.portState), + masterIdBuf, + tmpBuf, + ptpClock->slaveStats.ofmMean, + ptpClock->slaveStats.ofmStdDev); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), mpd); + if (ptpClock->portDS.delayMechanism == E2E) { + INFO("Status update: state %s, best master %s, mpd %s s, mpd_mean % .09f s, mpd_dev % .09f s\n", + portState_getName(ptpClock->portDS.portState), + masterIdBuf, + tmpBuf, + ptpClock->slaveStats.mpdMean, + ptpClock->slaveStats.mpdStdDev); + } else if(ptpClock->portDS.delayMechanism == P2P) { + INFO("Status update: state %s, best master %s, mpd %s s\n", portState_getName(ptpClock->portDS.portState), masterIdBuf, tmpBuf); + } + } else { + INFO("Status update: state %s, best master %s, ofm %s s\n", portState_getName(ptpClock->portDS.portState), masterIdBuf, tmpBuf); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), mpd); + if(ptpClock->portDS.delayMechanism != DELAY_DISABLED) { + INFO("Status update: state %s, best master %s, mpd %s s\n", portState_getName(ptpClock->portDS.portState), masterIdBuf, tmpBuf); + } + } +#else + INFO("Status update: state %s, best master %s, ofm %s s\n", portState_getName(ptpClock->portDS.portState), masterIdBuf, tmpBuf); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), mpd); + if(ptpClock->portDS.delayMechanism != DELAY_DISABLED) { + INFO("Status update: state %s, best master %s, mpd %s s\n", portState_getName(ptpClock->portDS.portState), masterIdBuf, tmpBuf); + } +#endif /* PTPD_STATISTICS */ + } else if(ptpClock->portDS.portState == PTP_MASTER) { + if(rtOpts->unicastNegotiation || ptpClock->unicastDestinationCount) { + INFO("Status update: state %s, %d slaves\n", portState_getName(ptpClock->portDS.portState), + ptpClock->unicastDestinationCount + ptpClock->slaveCount); + } else { + INFO("Status update: state %s\n", portState_getName(ptpClock->portDS.portState)); + } + } else { + INFO("Status update: state %s\n", portState_getName(ptpClock->portDS.portState)); + } +} + + +void +displayStatus(PtpClock *ptpClock, const char *prefixMessage) +{ + + static char sbuf[SCREEN_BUFSZ]; + char strAddr[MAXHOSTNAMELEN]; + int len = 0; + + memset(strAddr, 0, sizeof(strAddr)); + memset(sbuf, ' ', sizeof(sbuf)); + len += snprintf(sbuf + len, sizeof(sbuf) - len, "%s", prefixMessage); + len += snprintf(sbuf + len, sizeof(sbuf) - len, "%s", + portState_getName(ptpClock->portDS.portState)); + + if (ptpClock->portDS.portState >= PTP_MASTER) { + len += snprintf(sbuf + len, sizeof(sbuf) - len, ", Best master: "); + len += snprint_PortIdentity(sbuf + len, sizeof(sbuf) - len, + &ptpClock->parentDS.parentPortIdentity); + if(ptpClock->bestMaster && ptpClock->bestMaster->sourceAddr) { + struct in_addr tmpAddr; + tmpAddr.s_addr = ptpClock->bestMaster->sourceAddr; + inet_ntop(AF_INET, (struct sockaddr_in *)(&tmpAddr), strAddr, MAXHOSTNAMELEN); + len += snprintf(sbuf + len, sizeof(sbuf) - len, " (IPv4:%s)",strAddr); + } + } + if(ptpClock->portDS.portState == PTP_MASTER) + len += snprintf(sbuf + len, sizeof(sbuf) - len, " (self)"); + len += snprintf(sbuf + len, sizeof(sbuf) - len, "\n"); + NOTICE("%s",sbuf); +} + +#define STATUSPREFIX "%-19s:" +void +writeStatusFile(PtpClock *ptpClock,const RunTimeOpts *rtOpts, Boolean quiet) +{ + + char outBuf[2048]; + char tmpBuf[200]; + + int n = getAlarmSummary(NULL, 0, ptpClock->alarms, ALRM_MAX); + char alarmBuf[n]; + + getAlarmSummary(alarmBuf, n, ptpClock->alarms, ALRM_MAX); + + if(rtOpts->statusLog.logFP == NULL) + return; + + char timeStr[MAXTIMESTR]; + char hostName[MAXHOSTNAMELEN]; + + struct timeval now; + memset(hostName, 0, MAXHOSTNAMELEN); + + gethostname(hostName, MAXHOSTNAMELEN); + gettimeofday(&now, 0); + strftime(timeStr, MAXTIMESTR, "%a %b %d %X %Z %Y", localtime((time_t*)&now.tv_sec)); + + FILE* out = rtOpts->statusLog.logFP; + memset(outBuf, 0, sizeof(outBuf)); + + setbuf(out, outBuf); + if(ftruncate(fileno(out), 0) < 0) { + DBG("writeStatusFile: ftruncate() failed\n"); + } + rewind(out); + + fprintf(out, STATUSPREFIX" %s, PID %d\n","Host info", hostName, (int)getpid()); + fprintf(out, STATUSPREFIX" %s\n","Local time", timeStr); + strftime(timeStr, MAXTIMESTR, "%a %b %d %X %Z %Y", gmtime((time_t*)&now.tv_sec)); + fprintf(out, STATUSPREFIX" %s\n","Kernel time", timeStr); + fprintf(out, STATUSPREFIX" %s%s\n","Interface", rtOpts->ifaceName, + (rtOpts->backupIfaceEnabled && ptpClock->runningBackupInterface) ? " (backup)" : (rtOpts->backupIfaceEnabled)? + " (primary)" : ""); + fprintf(out, STATUSPREFIX" %s\n","Preset", dictionary_get(rtOpts->currentConfig, "ptpengine:preset", "")); + fprintf(out, STATUSPREFIX" %s%s","Transport", dictionary_get(rtOpts->currentConfig, "ptpengine:transport", ""), + (rtOpts->transport==UDP_IPV4 && rtOpts->pcap == TRUE)?" + libpcap":""); + + if(rtOpts->transport != IEEE_802_3) { + fprintf(out,", %s", dictionary_get(rtOpts->currentConfig, "ptpengine:ip_mode", "")); + fprintf(out,"%s", rtOpts->unicastNegotiation ? " negotiation":""); + } + + fprintf(out,"\n"); + + fprintf(out, STATUSPREFIX" %s\n","Delay mechanism", dictionary_get(rtOpts->currentConfig, "ptpengine:delay_mechanism", "")); + if(ptpClock->portDS.portState >= PTP_MASTER) { + fprintf(out, STATUSPREFIX" %s\n","Sync mode", ptpClock->defaultDS.twoStepFlag ? "TWO_STEP" : "ONE_STEP"); + } + if(ptpClock->defaultDS.slaveOnly && rtOpts->anyDomain) { + fprintf(out, STATUSPREFIX" %d, preferred %d\n","PTP domain", + ptpClock->defaultDS.domainNumber, rtOpts->domainNumber); + } else if(ptpClock->defaultDS.slaveOnly && rtOpts->unicastNegotiation) { + fprintf(out, STATUSPREFIX" %d, default %d\n","PTP domain", ptpClock->defaultDS.domainNumber, rtOpts->domainNumber); + } else { + fprintf(out, STATUSPREFIX" %d\n","PTP domain", ptpClock->defaultDS.domainNumber); + } + fprintf(out, STATUSPREFIX" %s\n","Port state", portState_getName(ptpClock->portDS.portState)); + if(strlen(alarmBuf) > 0) { + fprintf(out, STATUSPREFIX" %s\n","Alarms", alarmBuf); + } + memset(tmpBuf, 0, sizeof(tmpBuf)); + snprint_PortIdentity(tmpBuf, sizeof(tmpBuf), + &ptpClock->portDS.portIdentity); + fprintf(out, STATUSPREFIX" %s\n","Local port ID", tmpBuf); + + + if(ptpClock->portDS.portState >= PTP_MASTER) { + memset(tmpBuf, 0, sizeof(tmpBuf)); + snprint_PortIdentity(tmpBuf, sizeof(tmpBuf), + &ptpClock->parentDS.parentPortIdentity); + fprintf(out, STATUSPREFIX" %s","Best master ID", tmpBuf); + if(ptpClock->portDS.portState == PTP_MASTER) + fprintf(out," (self)"); + fprintf(out,"\n"); + } + if(rtOpts->transport == UDP_IPV4 && + ptpClock->portDS.portState > PTP_MASTER && + ptpClock->bestMaster && ptpClock->bestMaster->sourceAddr) { + { + struct in_addr tmpAddr; + tmpAddr.s_addr = ptpClock->bestMaster->sourceAddr; + fprintf(out, STATUSPREFIX" %s\n","Best master IP", inet_ntoa(tmpAddr)); + } + } + if(ptpClock->portDS.portState == PTP_SLAVE) { + fprintf(out, STATUSPREFIX" Priority1 %d, Priority2 %d, clockClass %d","GM priority", + ptpClock->parentDS.grandmasterPriority1, ptpClock->parentDS.grandmasterPriority2, ptpClock->parentDS.grandmasterClockQuality.clockClass); + if(rtOpts->unicastNegotiation && ptpClock->parentGrants != NULL ) { + fprintf(out, ", localPref %d", ptpClock->parentGrants->localPreference); + } + fprintf(out, "%s\n", (ptpClock->bestMaster != NULL && ptpClock->bestMaster->disqualified) ? " (timeout)" : ""); + } + + if(ptpClock->defaultDS.clockQuality.clockClass < 128 || + ptpClock->portDS.portState == PTP_SLAVE || + ptpClock->portDS.portState == PTP_PASSIVE){ + fprintf(out, STATUSPREFIX" ","Time properties"); + fprintf(out, "%s timescale, ",ptpClock->timePropertiesDS.ptpTimescale ? "PTP":"ARB"); + fprintf(out, "tracbl: time %s, freq %s, src: %s(0x%02x)\n", ptpClock->timePropertiesDS.timeTraceable ? "Y" : "N", + ptpClock->timePropertiesDS.frequencyTraceable ? "Y" : "N", + getTimeSourceName(ptpClock->timePropertiesDS.timeSource), + ptpClock->timePropertiesDS.timeSource); + fprintf(out, STATUSPREFIX" ","UTC properties"); + fprintf(out, "UTC valid: %s", ptpClock->timePropertiesDS.currentUtcOffsetValid ? "Y" : "N"); + fprintf(out, ", UTC offset: %d",ptpClock->timePropertiesDS.currentUtcOffset); + fprintf(out, "%s",ptpClock->timePropertiesDS.leap61 ? + ", LEAP61 pending" : ptpClock->timePropertiesDS.leap59 ? ", LEAP59 pending" : ""); + if (ptpClock->portDS.portState == PTP_SLAVE) { + fprintf(out, "%s", rtOpts->preferUtcValid ? ", prefer UTC" : ""); + fprintf(out, "%s", rtOpts->requireUtcValid ? ", require UTC" : ""); + } + fprintf(out,"\n"); + } + + if(ptpClock->portDS.portState == PTP_SLAVE) { + memset(tmpBuf, 0, sizeof(tmpBuf)); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), + &ptpClock->currentDS.offsetFromMaster); + fprintf(out, STATUSPREFIX" %s s","Offset from Master", tmpBuf); +#ifdef PTPD_STATISTICS + if(ptpClock->slaveStats.statsCalculated) + fprintf(out, ", mean % .09f s, dev % .09f s", + ptpClock->slaveStats.ofmMean, + ptpClock->slaveStats.ofmStdDev + ); +#endif /* PTPD_STATISTICS */ + fprintf(out,"\n"); + + if(ptpClock->portDS.delayMechanism == E2E) { + memset(tmpBuf, 0, sizeof(tmpBuf)); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), + &ptpClock->currentDS.meanPathDelay); + fprintf(out, STATUSPREFIX" %s s","Mean Path Delay", tmpBuf); +#ifdef PTPD_STATISTICS + if(ptpClock->slaveStats.statsCalculated) + fprintf(out, ", mean % .09f s, dev % .09f s", + ptpClock->slaveStats.mpdMean, + ptpClock->slaveStats.mpdStdDev + ); +#endif /* PTPD_STATISTICS */ + fprintf(out,"\n"); + } + if(ptpClock->portDS.delayMechanism == P2P) { + memset(tmpBuf, 0, sizeof(tmpBuf)); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), + &ptpClock->portDS.peerMeanPathDelay); + fprintf(out, STATUSPREFIX" %s s","Mean Path (p)Delay", tmpBuf); + fprintf(out,"\n"); + } + + fprintf(out, STATUSPREFIX" ","Clock status"); + if(rtOpts->enablePanicMode) { + if(ptpClock->panicMode) { + fprintf(out,"panic mode,"); + + } + } + if(rtOpts->calibrationDelay) { + fprintf(out, "%s, ", + ptpClock->isCalibrated ? "calibrated" : "not calibrated"); + } + fprintf(out, "%s", + ptpClock->clockControl.granted ? "in control" : "no control"); + if(rtOpts->noAdjust) { + fprintf(out, ", read-only"); + } +#ifdef PTPD_STATISTICS + else { + if (rtOpts->servoStabilityDetection) { + fprintf(out, ", %s", + ptpClock->servo.isStable ? "stabilised" : "not stabilised"); + } + } +#endif /* PTPD_STATISTICS */ + fprintf(out,"\n"); + + + fprintf(out, STATUSPREFIX" % .03f ppm","Clock correction", + ptpClock->servo.observedDrift / 1000.0); +if(ptpClock->servo.runningMaxOutput) + fprintf(out, " (slewing at maximum rate)"); +else { +#ifdef PTPD_STATISTICS + if(ptpClock->slaveStats.statsCalculated) + fprintf(out, ", mean % .03f ppm, dev % .03f ppm", + ptpClock->servo.driftMean / 1000.0, + ptpClock->servo.driftStdDev / 1000.0 + ); + if(rtOpts->servoStabilityDetection) { + fprintf(out, ", dev thr % .03f ppm", + ptpClock->servo.stabilityThreshold / 1000.0 + ); + } +#endif /* PTPD_STATISTICS */ +} + fprintf(out,"\n"); + + + } + + + + if(ptpClock->portDS.portState == PTP_MASTER || ptpClock->portDS.portState == PTP_PASSIVE) { + + fprintf(out, STATUSPREFIX" %d","Priority1 ", ptpClock->defaultDS.priority1); + if(ptpClock->portDS.portState == PTP_PASSIVE) + fprintf(out, " (best master: %d)", ptpClock->parentDS.grandmasterPriority1); + fprintf(out,"\n"); + fprintf(out, STATUSPREFIX" %d","Priority2 ", ptpClock->defaultDS.priority2); + if(ptpClock->portDS.portState == PTP_PASSIVE) + fprintf(out, " (best master: %d)", ptpClock->parentDS.grandmasterPriority2); + fprintf(out,"\n"); + fprintf(out, STATUSPREFIX" %d","ClockClass ", ptpClock->defaultDS.clockQuality.clockClass); + if(ptpClock->portDS.portState == PTP_PASSIVE) + fprintf(out, " (best master: %d)", ptpClock->parentDS.grandmasterClockQuality.clockClass); + fprintf(out,"\n"); + if(ptpClock->portDS.delayMechanism == P2P) { + memset(tmpBuf, 0, sizeof(tmpBuf)); + snprint_TimeInternal(tmpBuf, sizeof(tmpBuf), + &ptpClock->portDS.peerMeanPathDelay); + fprintf(out, STATUSPREFIX" %s s","Mean Path (p)Delay", tmpBuf); + fprintf(out,"\n"); + } + + } + + if(ptpClock->portDS.portState == PTP_MASTER || ptpClock->portDS.portState == PTP_PASSIVE || + ptpClock->portDS.portState == PTP_SLAVE) { + + fprintf(out, STATUSPREFIX" ","Message rates"); + + if (ptpClock->portDS.logSyncInterval == UNICAST_MESSAGEINTERVAL) + fprintf(out,"[UC-unknown]"); + else if (ptpClock->portDS.logSyncInterval <= 0) + fprintf(out,"%.0f/s",pow(2,-ptpClock->portDS.logSyncInterval)); + else + fprintf(out,"1/%.0fs",pow(2,ptpClock->portDS.logSyncInterval)); + fprintf(out, " sync"); + + + if(ptpClock->portDS.delayMechanism == E2E) { + if (ptpClock->portDS.logMinDelayReqInterval == UNICAST_MESSAGEINTERVAL) + fprintf(out,", [UC-unknown]"); + else if (ptpClock->portDS.logMinDelayReqInterval <= 0) + fprintf(out,", %.0f/s",pow(2,-ptpClock->portDS.logMinDelayReqInterval)); + else + fprintf(out,", 1/%.0fs",pow(2,ptpClock->portDS.logMinDelayReqInterval)); + fprintf(out, " delay"); + } + + if(ptpClock->portDS.delayMechanism == P2P) { + if (ptpClock->portDS.logMinPdelayReqInterval == UNICAST_MESSAGEINTERVAL) + fprintf(out,", [UC-unknown]"); + else if (ptpClock->portDS.logMinPdelayReqInterval <= 0) + fprintf(out,", %.0f/s",pow(2,-ptpClock->portDS.logMinPdelayReqInterval)); + else + fprintf(out,", 1/%.0fs",pow(2,ptpClock->portDS.logMinPdelayReqInterval)); + fprintf(out, " pdelay"); + } + + if (ptpClock->portDS.logAnnounceInterval == UNICAST_MESSAGEINTERVAL) + fprintf(out,", [UC-unknown]"); + else if (ptpClock->portDS.logAnnounceInterval <= 0) + fprintf(out,", %.0f/s",pow(2,-ptpClock->portDS.logAnnounceInterval)); + else + fprintf(out,", 1/%.0fs",pow(2,ptpClock->portDS.logAnnounceInterval)); + fprintf(out, " announce"); + + fprintf(out,"\n"); + + } + + fprintf(out, STATUSPREFIX" ","TimingService"); + + fprintf(out, "current %s, best %s, pref %s", (timingDomain.current != NULL) ? timingDomain.current->id : "none", + (timingDomain.best != NULL) ? timingDomain.best->id : "none", + (timingDomain.preferred != NULL) ? timingDomain.preferred->id : "none"); + + if((timingDomain.current != NULL) && + (timingDomain.current->holdTimeLeft > 0)) { + fprintf(out, ", hold %d sec", timingDomain.current->holdTimeLeft); + } else if(timingDomain.electionLeft) { + fprintf(out, ", elec %d sec", timingDomain.electionLeft); + } + + fprintf(out, "\n"); + + fprintf(out, STATUSPREFIX" ","TimingServices"); + + fprintf(out, "total %d, avail %d, oper %d, idle %d, in_ctrl %d%s\n", + timingDomain.serviceCount, + timingDomain.availableCount, + timingDomain.operationalCount, + timingDomain.idleCount, + timingDomain.controlCount, + timingDomain.controlCount > 1 ? " (!)":""); + + fprintf(out, STATUSPREFIX" ","Performance"); + fprintf(out,"Message RX %d/s, TX %d/s", ptpClock->counters.messageReceiveRate, + ptpClock->counters.messageSendRate); + if(ptpClock->portDS.portState == PTP_MASTER) { + if(rtOpts->unicastNegotiation) { + fprintf(out,", slaves %d", ptpClock->slaveCount); + } else if (rtOpts->ipMode == IPMODE_UNICAST) { + fprintf(out,", slaves %d", ptpClock->unicastDestinationCount); + } + } + + fprintf(out,"\n"); + + if ( ptpClock->portDS.portState == PTP_SLAVE || + ptpClock->defaultDS.clockQuality.clockClass == 255 ) { + + fprintf(out, STATUSPREFIX" %lu\n","Announce received", + (unsigned long)ptpClock->counters.announceMessagesReceived); + fprintf(out, STATUSPREFIX" %lu\n","Sync received", + (unsigned long)ptpClock->counters.syncMessagesReceived); + if(ptpClock->defaultDS.twoStepFlag) + fprintf(out, STATUSPREFIX" %lu\n","Follow-up received", + (unsigned long)ptpClock->counters.followUpMessagesReceived); + if(ptpClock->portDS.delayMechanism == E2E) { + fprintf(out, STATUSPREFIX" %lu\n","DelayReq sent", + (unsigned long)ptpClock->counters.delayReqMessagesSent); + fprintf(out, STATUSPREFIX" %lu\n","DelayResp received", + (unsigned long)ptpClock->counters.delayRespMessagesReceived); + } + } + + if( ptpClock->portDS.portState == PTP_MASTER || + ptpClock->defaultDS.clockQuality.clockClass < 128 ) { + fprintf(out, STATUSPREFIX" %lu received, %lu sent \n","Announce", + (unsigned long)ptpClock->counters.announceMessagesReceived, + (unsigned long)ptpClock->counters.announceMessagesSent); + fprintf(out, STATUSPREFIX" %lu\n","Sync sent", + (unsigned long)ptpClock->counters.syncMessagesSent); + if(ptpClock->defaultDS.twoStepFlag) + fprintf(out, STATUSPREFIX" %lu\n","Follow-up sent", + (unsigned long)ptpClock->counters.followUpMessagesSent); + + if(ptpClock->portDS.delayMechanism == E2E) { + fprintf(out, STATUSPREFIX" %lu\n","DelayReq received", + (unsigned long)ptpClock->counters.delayReqMessagesReceived); + fprintf(out, STATUSPREFIX" %lu\n","DelayResp sent", + (unsigned long)ptpClock->counters.delayRespMessagesSent); + } + + } + + if(ptpClock->portDS.delayMechanism == P2P) { + + fprintf(out, STATUSPREFIX" %lu received, %lu sent\n","PdelayReq", + (unsigned long)ptpClock->counters.pdelayReqMessagesReceived, + (unsigned long)ptpClock->counters.pdelayReqMessagesSent); + fprintf(out, STATUSPREFIX" %lu received, %lu sent\n","PdelayResp", + (unsigned long)ptpClock->counters.pdelayRespMessagesReceived, + (unsigned long)ptpClock->counters.pdelayRespMessagesSent); + fprintf(out, STATUSPREFIX" %lu received, %lu sent\n","PdelayRespFollowUp", + (unsigned long)ptpClock->counters.pdelayRespFollowUpMessagesReceived, + (unsigned long)ptpClock->counters.pdelayRespFollowUpMessagesSent); + + } + + if(ptpClock->counters.domainMismatchErrors) + fprintf(out, STATUSPREFIX" %lu\n","Domain Mismatches", + (unsigned long)ptpClock->counters.domainMismatchErrors); + + if(ptpClock->counters.ignoredAnnounce) + fprintf(out, STATUSPREFIX" %lu\n","Ignored Announce", + (unsigned long)ptpClock->counters.ignoredAnnounce); + + if(ptpClock->counters.unicastGrantsDenied) + fprintf(out, STATUSPREFIX" %lu\n","Denied Unicast", + (unsigned long)ptpClock->counters.unicastGrantsDenied); + + fprintf(out, STATUSPREFIX" %lu\n","State transitions", + (unsigned long)ptpClock->counters.stateTransitions); + fprintf(out, STATUSPREFIX" %lu\n","PTP Engine resets", + (unsigned long)ptpClock->resetCount); + + + fflush(out); +} + +void +displayPortIdentity(PortIdentity *port, const char *prefixMessage) +{ + static char sbuf[SCREEN_BUFSZ]; + int len = 0; + + memset(sbuf, ' ', sizeof(sbuf)); + len += snprintf(sbuf + len, sizeof(sbuf) - len, "%s ", prefixMessage); + len += snprint_PortIdentity(sbuf + len, sizeof(sbuf) - len, port); + len += snprintf(sbuf + len, sizeof(sbuf) - len, "\n"); + INFO("%s",sbuf); +} + + +void +recordSync(UInteger16 sequenceId, TimeInternal * time) +{ + extern RunTimeOpts rtOpts; + if (rtOpts.recordLog.logEnabled && rtOpts.recordLog.logFP != NULL) { + fprintf(rtOpts.recordLog.logFP, "%d %llu\n", sequenceId, + ((time->seconds * 1000000000ULL) + time->nanoseconds) + ); + maintainLogSize(&rtOpts.recordLog); + } +} + +Boolean +nanoSleep(TimeInternal * t) +{ + struct timespec ts, tr; + + ts.tv_sec = t->seconds; + ts.tv_nsec = t->nanoseconds; + + if (nanosleep(&ts, &tr) < 0) { + t->seconds = tr.tv_sec; + t->nanoseconds = tr.tv_nsec; + return FALSE; + } + return TRUE; +} + +#ifdef __QNXNTO__ + +static const struct sigevent* timerIntHandler(void* data, int id) { + struct timespec tp; + TimerIntData* myData = (TimerIntData*)data; + uint64_t new_tsc = ClockCycles(); + + clock_gettime(CLOCK_REALTIME, &tp); + + if(new_tsc > myData->prev_tsc) { + myData->cur_delta = new_tsc - myData->prev_tsc; + /* when hell freezeth over, thy TSC shall roll over */ + } else { + myData->cur_delta = myData->prev_delta; + } + /* 4/6 weighted average */ + myData->filtered_delta = (40 * myData->cur_delta + 60 * myData->prev_delta) / 100; + myData->prev_delta = myData->cur_delta; + myData->prev_tsc = new_tsc; + + if(myData->counter < 2) { + myData->counter++; + } + + myData->last_clock = timespec2nsec(&tp); + return NULL; + +} +#endif + + void getTime(TimeInternal *time) + { +#ifdef __QNXNTO__ + static TimerIntData tmpData; + int ret; + uint64_t delta; + double tick_delay; + uint64_t clock_offset; + struct timespec tp; + + if(!tDataUpdated) { + memset(&tData, 0, sizeof(TimerIntData)); + if(ThreadCtl(_NTO_TCTL_IO, 0) == -1) { + ERROR("QNX: could not give process I/O privileges"); + return; + } + + tData.cps = SYSPAGE_ENTRY(qtime)->cycles_per_sec; + tData.ns_per_tick = 1000000000.0 / tData.cps; + tData.prev_tsc = ClockCycles(); + clock_gettime(CLOCK_REALTIME, &tp); + tData.last_clock = timespec2nsec(&tp); + + ret = InterruptAttach(SYSPAGE_ENTRY(qtime)->intr, timerIntHandler, &tData, sizeof(TimerIntData), _NTO_INTR_FLAGS_END | _NTO_INTR_FLAGS_TRK_MSK); + + if(ret == -1) { + ERROR("QNX: could not attach to timer interrupt"); + return ; + } + else { + printf("Attached to timer interrupt vector %d\n", SYSPAGE_ENTRY(qtime)->intr); + } + + tDataUpdated = TRUE; + time->seconds = tp.tv_sec; + time->nanoseconds = tp.tv_nsec; + return; + } + + memcpy(&tmpData, &tData, sizeof(TimerIntData)); + + delta = ClockCycles() - tmpData.prev_tsc; + + /* compute time since last clock update */ + tick_delay = (double)delta / (double)tmpData.filtered_delta; + clock_offset = (uint64_t)(tick_delay * tmpData.ns_per_tick * (double)tmpData.filtered_delta); + + /* not filtered yet */ + if(tData.counter < 2) { + clock_offset = 0; + } + + DBGV("QNX getTime cps: %lld tick interval: %.09f, time since last tick: %lld\n", + tmpData.cps, tmpData.filtered_delta * tmpData.ns_per_tick, clock_offset); + + nsec2timespec(&tp, tmpData.last_clock + clock_offset); + + time->seconds = tp.tv_sec; + time->nanoseconds = tp.tv_nsec; + return; +#else + +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + + struct timespec tp; + if (clock_gettime(CLOCK_REALTIME, &tp) < 0) { + PERROR("clock_gettime() failed, exiting."); + exit(0); + } + time->seconds = tp.tv_sec; + time->nanoseconds = tp.tv_nsec; + +#else + + struct timeval tv; + gettimeofday(&tv, 0); + time->seconds = tv.tv_sec; + time->nanoseconds = tv.tv_usec * 1000; + +#endif /* _POSIX_TIMERS */ +#endif /* __QNXNTO__ */ +} + +void +getTimeMonotonic(TimeInternal * time) +{ +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + + struct timespec tp; +#ifndef CLOCK_MONOTINIC + if (clock_gettime(CLOCK_REALTIME, &tp) < 0) { +#else + if (clock_gettime(CLOCK_MONOTONIC, &tp) < 0) { +#endif /* CLOCK_MONOTONIC */ + PERROR("clock_gettime() failed, exiting."); + exit(0); + } + time->seconds = tp.tv_sec; + time->nanoseconds = tp.tv_nsec; +#else + + struct timeval tv; + gettimeofday(&tv, 0); + time->seconds = tv.tv_sec; + time->nanoseconds = tv.tv_usec * 1000; + +#endif /* _POSIX_TIMERS */ +} + + +void +setTime(TimeInternal * time) +{ + +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + + struct timespec tp; + tp.tv_sec = time->seconds; + tp.tv_nsec = time->nanoseconds; + +#else + + struct timeval tv; + tv.tv_sec = time->seconds; + tv.tv_usec = time->nanoseconds / 1000; + +#endif /* _POSIX_TIMERS */ + +#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + + if (clock_settime(CLOCK_REALTIME, &tp) < 0) { + PERROR("Could not set system time"); + return; + } + +#else + + settimeofday(&tv, 0); + +#endif /* _POSIX_TIMERS */ + + struct timespec tmpTs = { time->seconds,0 }; + + char timeStr[MAXTIMESTR]; + strftime(timeStr, MAXTIMESTR, "%x %X", localtime(&tmpTs.tv_sec)); + WARNING("Stepped the system clock to: %s.%d\n", + timeStr, time->nanoseconds); + +} + +#ifdef HAVE_LINUX_RTC_H + +/* Set the RTC to the desired time time */ +void setRtc(TimeInternal *timeToSet) +{ + + static Boolean deviceFound = FALSE; + static char* rtcDev; + struct tm* tmTime; + time_t seconds; + int rtcFd; + struct stat statBuf; + + if (!deviceFound) { + if(stat("/dev/misc/rtc", &statBuf) == 0) { + rtcDev="/dev/misc/rtc\0"; + deviceFound = TRUE; + } else if(stat("/dev/rtc", &statBuf) == 0) { + rtcDev="/dev/rtc\0"; + deviceFound = TRUE; + } else if(stat("/dev/rtc0", &statBuf) == 0) { + rtcDev="/dev/rtc0\0"; + deviceFound = TRUE; + } else { + + ERROR("Could not set RTC time - no suitable rtc device found\n"); + return; + } + + if(!S_ISCHR(statBuf.st_mode)) { + ERROR("Could not set RTC time - device %s is not a character device\n", + rtcDev); + deviceFound = FALSE; + return; + } + + } + + DBGV("Usable RTC device: %s\n",rtcDev); + + if(timeToSet->seconds == 0 && timeToSet->nanoseconds==0) { + getTime(timeToSet); + } + + + + if((rtcFd = open(rtcDev, O_RDONLY)) < 0) { + PERROR("Could not set RTC time: error opening %s", rtcDev); + return; + } + + seconds = (time_t)timeToSet->seconds; + if(timeToSet->nanoseconds >= 500000) seconds++; + tmTime = gmtime(&seconds); + + DBGV("Set RTC from %d seconds to y: %d m: %d d: %d \n",timeToSet->seconds,tmTime->tm_year,tmTime->tm_mon,tmTime->tm_mday); + + if(ioctl(rtcFd, RTC_SET_TIME, tmTime) < 0) { + PERROR("Could not set RTC time on %s - ioctl failed", rtcDev); + goto cleanup; + } + + NOTIFY("Succesfully set RTC time using %s\n", rtcDev); + +cleanup: + + close(rtcFd); + +} + +#endif /* HAVE_LINUX_RTC_H */ + +/* returns a double beween 0.0 and 1.0 */ +double +getRand(void) +{ + return((rand() * 1.0) / RAND_MAX); +} + +/* Attempt setting advisory write lock on a file descriptor*/ +int +lockFile(int fd) +{ + struct flock fl; + + fl.l_type = DEFAULT_LOCKMODE; + fl.l_start = 0; + fl.l_whence = SEEK_SET; + fl.l_len = 0; + return(fcntl(fd, F_SETLK, &fl)); +} + +/* + * Check if a file descriptor's lock flags match specified flags, + * do not acquire lock - just query. If the flags match, populate + * lockPid with the PID of the process already holding the lock(s) + * Return values: -1 = error, 0 = locked, 1 = not locked + */ +int checkLockStatus(int fd, short lockType, int *lockPid) { + + struct flock fl; + + memset(&fl, 0, sizeof(fl)); + + if(fcntl(fd, F_GETLK, &fl) == -1) + { + return -1; + } + /* Return 0 if file is already locked, but not by us */ + if((lockType & fl.l_type) && fl.l_pid != getpid()) { + *lockPid = fl.l_pid; + return 0; + } + + return 1; + +} + +/* + * Check if it's possible to acquire lock on a file - if already locked, + * populate lockPid with the PID currently holding the write lock. + */ +int +checkFileLockable(const char *fileName, int *lockPid) { + + FILE* fileHandle; + int ret; + + if((fileHandle = fopen(fileName,"w+")) == NULL) { + PERROR("Could not open %s for writing\n"); + return -1; + } + + ret = checkLockStatus(fileno(fileHandle), DEFAULT_LOCKMODE, lockPid); + if (ret == -1) { + PERROR("Could not check lock status of %s",fileName); + } + + fclose(fileHandle); + return ret; +} + +/* + * If automatic lock files are used, check for potential conflicts + * based on already existing lock files containing our interface name + * or clock driver name + */ +Boolean +checkOtherLocks(RunTimeOpts* rtOpts) +{ + + +char searchPattern[PATH_MAX]; +char * lockPath = 0; +int lockPid = 0; +glob_t matchedFiles; +Boolean ret = TRUE; +int matches = 0, counter = 0; + + /* no need to check locks */ + if(rtOpts->ignore_daemon_lock || + !rtOpts->autoLockFile) + return TRUE; + + /* + * Try to discover if we can run in desired mode + * based on the presence of other lock files + * and them being lockable + */ + + /* Check for other ptpd running on the same interface - same for all modes */ + snprintf(searchPattern, PATH_MAX,"%s/%s_*_%s.lock", + rtOpts->lockDirectory, PTPD_PROGNAME,rtOpts->ifaceName); + + DBGV("SearchPattern: %s\n",searchPattern); + switch(glob(searchPattern, 0, NULL, &matchedFiles)) { + + case GLOB_NOSPACE: + case GLOB_ABORTED: + PERROR("Could not scan %s directory\n", rtOpts->lockDirectory);; + ret = FALSE; + goto end; + default: + break; + } + + counter = matchedFiles.gl_pathc; + matches = counter; + while (matches--) { + lockPath=matchedFiles.gl_pathv[matches]; + DBG("matched: %s\n",lockPath); + /* check if there is a lock file with our NIC in the name */ + switch(checkFileLockable(lockPath, &lockPid)) { + /* Could not check lock status */ + case -1: + ERROR("Looks like "USER_DESCRIPTION" may be already running on %s: %s found, but could not check lock\n", + rtOpts->ifaceName, lockPath); + ret = FALSE; + goto end; + /* It was possible to acquire lock - file looks abandoned */ + case 1: + DBG("Lock file %s found, but is not locked for writing.\n", lockPath); + ret = TRUE; + break; + /* file is locked */ + case 0: + ERROR("Looks like "USER_DESCRIPTION" is already running on %s: %s found and is locked by pid %d\n", + rtOpts->ifaceName, lockPath, lockPid); + ret = FALSE; + goto end; + } + } + + if(matches > 0) + globfree(&matchedFiles); + /* Any mode that can control the clock - also check the clock driver */ + if(rtOpts->clockQuality.clockClass > 127 ) { + int res = snprintf(searchPattern, PATH_MAX,"%s/%s_%s_*.lock", + rtOpts->lockDirectory,PTPD_PROGNAME,DEFAULT_CLOCKDRIVER); + if(res < 0) { + ERROR("Print to searchPattern buffer truncated!"); + } + DBGV("SearchPattern: %s\n",searchPattern); + + switch(glob(searchPattern, 0, NULL, &matchedFiles)) { + + case GLOB_NOSPACE: + case GLOB_ABORTED: + PERROR("Could not scan %s directory\n", rtOpts->lockDirectory);; + ret = FALSE; + goto end; + default: + break; + } + counter = matchedFiles.gl_pathc; + matches = counter; + while (counter--) { + lockPath=matchedFiles.gl_pathv[counter]; + DBG("matched: %s\n",lockPath); + /* Check if there is a lock file with our clock driver in the name */ + switch(checkFileLockable(lockPath, &lockPid)) { + /* could not check lock status */ + case -1: + ERROR("Looks like "USER_DESCRIPTION" may already be controlling the \""DEFAULT_CLOCKDRIVER + "\" clock: %s found, but could not check lock status.\n", lockPath); + ret = FALSE; + goto end; + /* it was possible to acquire lock - looks abandoned */ + case 1: + DBG("Lock file %s found, but is not locked for writing.\n", lockPath); + ret = TRUE; + break; + /* file is locked */ + case 0: + ERROR("Looks like "USER_DESCRIPTION" is already controlling the \""DEFAULT_CLOCKDRIVER + "\" clock: %s found and is locked by pid %d\n", lockPath, lockPid); + default: + ret = FALSE; + goto end; + } + } + } + + ret = TRUE; + +end: + if(matches > 0) { + globfree(&matchedFiles); + } + return ret; +} + + +/* Whole block of adjtimex() functions starts here - only for systems with sys/timex.h */ + +#ifdef HAVE_SYS_TIMEX_H + +/* + * Apply a tick / frequency shift to the kernel clock + */ + +Boolean +adjFreq(double adj) +{ + + extern RunTimeOpts rtOpts; + struct timex t; + +#ifdef HAVE_STRUCT_TIMEX_TICK + Integer32 tickAdj = 0; + +#ifdef PTPD_DBG2 + double oldAdj = adj; +#endif + +#endif /* HAVE_STRUCT_TIMEX_TICK */ + + memset(&t, 0, sizeof(t)); + + /* Clamp to max PPM */ + if (adj > rtOpts.servoMaxPpb){ + adj = rtOpts.servoMaxPpb; + } else if (adj < -rtOpts.servoMaxPpb){ + adj = -rtOpts.servoMaxPpb; + } + +/* Y U NO HAVE TICK? */ +#ifdef HAVE_STRUCT_TIMEX_TICK + + /* Get the USER_HZ value */ + Integer32 userHZ = sysconf(_SC_CLK_TCK); + + /* + * Get the tick resolution (ppb) - offset caused by changing the tick value by 1. + * The ticks value is the duration of one tick in us. So with userHz = 100 ticks per second, + * change of ticks by 1 (us) means a 100 us frequency shift = 100 ppm = 100000 ppb. + * For userHZ = 1000, change by 1 is a 1ms offset (10 times more ticks per second) + */ + Integer32 tickRes = userHZ * 1000; + + /* + * If we are outside the standard +/-512ppm, switch to a tick + freq combination: + * Keep moving ticks from adj to tickAdj until we get back to the normal range. + * The offset change will not be super smooth as we flip between tick and frequency, + * but this in general should only be happening under extreme conditions when dragging the + * offset down from very large values. When maxPPM is left at the default value, behaviour + * is the same as previously, clamped to 512ppm, but we keep tick at the base value, + * preventing long stabilisation times say when we had a non-default tick value left over + * from a previous NTP run. + */ + if (adj > ADJ_FREQ_MAX){ + while (adj > ADJ_FREQ_MAX) { + tickAdj++; + adj -= tickRes; + } + + } else if (adj < -ADJ_FREQ_MAX){ + while (adj < -ADJ_FREQ_MAX) { + tickAdj--; + adj += tickRes; + } + } + /* Base tick duration - 10000 when userHZ = 100 */ + t.tick = 1E6 / userHZ; + /* Tick adjustment if necessary */ + t.tick += tickAdj; + + + t.modes = ADJ_TICK; + +#endif /* HAVE_STRUCT_TIMEX_TICK */ + + t.modes |= MOD_FREQUENCY; + + double dFreq = adj * ((1 << 16) / 1000.0); + t.freq = (int) round(dFreq); +#ifdef HAVE_STRUCT_TIMEX_TICK + DBG2("adjFreq: oldadj: %.09f, newadj: %.09f, tick: %d, tickadj: %d\n", oldAdj, adj,t.tick,tickAdj); +#endif /* HAVE_STRUCT_TIMEX_TICK */ + DBG2(" adj is %.09f; t freq is %d (float: %.09f)\n", adj, t.freq, dFreq); + + return !adjtimex(&t); +} + + +double +getAdjFreq(void) +{ + struct timex t; + double dFreq; + + DBGV("getAdjFreq called\n"); + + memset(&t, 0, sizeof(t)); + t.modes = 0; + adjtimex(&t); + + dFreq = (t.freq + 0.0) / ((1<<16) / 1000.0); + + DBGV(" kernel adj is: %f, kernel freq is: %d\n", + dFreq, t.freq); + + return(dFreq); +} + + +/* First cut on informing the clock */ +void +informClockSource(PtpClock* ptpClock) +{ + struct timex tmx; + + memset(&tmx, 0, sizeof(tmx)); + + tmx.modes = MOD_MAXERROR | MOD_ESTERROR; + + tmx.maxerror = (ptpClock->currentDS.offsetFromMaster.seconds * 1E9 + + ptpClock->currentDS.offsetFromMaster.nanoseconds) / 1000; + tmx.esterror = tmx.maxerror; + + if (adjtimex(&tmx) < 0) + DBG("informClockSource: could not set adjtimex flags: %s", strerror(errno)); +} + + +void +unsetTimexFlags(int flags, Boolean quiet) +{ + struct timex tmx; + int ret; + + memset(&tmx, 0, sizeof(tmx)); + + tmx.modes = MOD_STATUS; + + tmx.status = getTimexFlags(); + if(tmx.status == -1) + return; + /* unset all read-only flags */ + tmx.status &= ~STA_RONLY; + tmx.status &= ~flags; + + ret = adjtimex(&tmx); + + if (ret < 0) + PERROR("Could not unset adjtimex flags: %s", strerror(errno)); + + if(!quiet && ret > 2) { + switch (ret) { + case TIME_OOP: + WARNING("Adjtimex: leap second already in progress\n"); + break; + case TIME_WAIT: + WARNING("Adjtimex: leap second already occurred\n"); + break; +#if !defined(TIME_BAD) + case TIME_ERROR: +#else + case TIME_BAD: +#endif /* TIME_BAD */ + default: + DBGV("unsetTimexFlags: adjtimex() returned TIME_BAD\n"); + break; + } + } +} + +int getTimexFlags(void) +{ + struct timex tmx; + int ret; + + memset(&tmx, 0, sizeof(tmx)); + + tmx.modes = 0; + ret = adjtimex(&tmx); + if (ret < 0) { + PERROR("Could not read adjtimex flags: %s", strerror(errno)); + return(-1); + + } + return( tmx.status ); +} + +Boolean +checkTimexFlags(int flags) { + int tflags = getTimexFlags(); + + if (tflags == -1) + return FALSE; + return ((tflags & flags) == flags); +} + +/* + * TODO: track NTP API changes - NTP API version check + * is required - the method of setting the TAI offset + * may change with next API versions + */ + +#if defined(MOD_TAI) && NTP_API == 4 +void +setKernelUtcOffset(int utc_offset) { + + struct timex tmx; + int ret; + + memset(&tmx, 0, sizeof(tmx)); + + tmx.modes = MOD_TAI; + tmx.constant = utc_offset; + + DBG2("Kernel NTP API supports TAI offset. " + "Setting TAI offset to %d", utc_offset); + + ret = adjtimex(&tmx); + + if (ret < 0) { + PERROR("Could not set kernel TAI offset: %s", strerror(errno)); + } +} +Boolean +getKernelUtcOffset(int *utc_offset) { + + static Boolean warned = FALSE; + int ret; + +#if defined(HAVE_NTP_GETTIME) + struct ntptimeval ntpv; + memset(&ntpv, 0, sizeof(ntpv)); + ret = ntp_gettime(&ntpv); +#else + struct timex tmx; + memset(&tmx, 0, sizeof(tmx)); + tmx.modes = 0; + ret = adjtimex(&tmx); +#endif /* HAVE_NTP_GETTIME */ + + if (ret < 0) { + if(!warned) { + PERROR("Could not read adjtimex/ntp_gettime flags: %s", strerror(errno)); + } + warned = TRUE; + return FALSE; + + } +#if !defined(HAVE_NTP_GETTIME) && defined(HAVE_STRUCT_TIMEX_TAI) + *utc_offset = ( tmx.tai ); + return TRUE; +#elif defined(HAVE_NTP_GETTIME) && defined(HAVE_STRUCT_NTPTIMEVAL_TAI) + *utc_offset = (int)(ntpv.tai); + return TRUE; +#endif /* HAVE_STRUCT_TIMEX_TAI */ + if(!warned) { + WARNING("No OS support for kernel TAI/UTC offset information. Cannot read UTC offset.\n"); + } + warned = TRUE; + return FALSE; +} +#endif /* MOD_TAI */ + +void +setTimexFlags(int flags, Boolean quiet) +{ + struct timex tmx; + int ret; + + memset(&tmx, 0, sizeof(tmx)); + + tmx.modes = MOD_STATUS; + + tmx.status = getTimexFlags(); + if(tmx.status == -1) + return; + /* unset all read-only flags */ + tmx.status &= ~STA_RONLY; + tmx.status |= flags; + + ret = adjtimex(&tmx); + + if (ret < 0) + PERROR("Could not set adjtimex flags: %s", strerror(errno)); + + if(!quiet && ret > 2) { + switch (ret) { + case TIME_OOP: + WARNING("Adjtimex: leap second already in progress\n"); + break; + case TIME_WAIT: + WARNING("Adjtimex: leap second already occurred\n"); + break; +#if !defined(TIME_BAD) + case TIME_ERROR: +#else + case TIME_BAD: +#endif /* TIME_BAD */ + default: + DBGV("unsetTimexFlags: adjtimex() returned TIME_BAD\n"); + break; + } + } +} + +#endif /* SYS_TIMEX_H */ + +#define DRIFTFORMAT "%.0f" + +void +restoreDrift(PtpClock * ptpClock, const RunTimeOpts * rtOpts, Boolean quiet) +{ + + FILE *driftFP; + Boolean reset_offset = FALSE; + double recovered_drift; + + DBGV("restoreDrift called\n"); + + if (ptpClock->drift_saved && rtOpts->drift_recovery_method > 0 ) { + ptpClock->servo.observedDrift = ptpClock->last_saved_drift; + if (!rtOpts->noAdjust && ptpClock->clockControl.granted) { + adjFreq_wrapper(rtOpts, ptpClock, -ptpClock->last_saved_drift); + } + DBG("loaded cached drift\n"); + return; + } + + switch (rtOpts->drift_recovery_method) { + + case DRIFT_FILE: + + if( (driftFP = fopen(rtOpts->driftFile,"r")) == NULL) { + if(errno!=ENOENT) { + PERROR("Could not open drift file: %s - using current kernel frequency offset. Ignore this error if ", + rtOpts->driftFile); + } else { + NOTICE("Drift file %s not found - will be initialised on write\n",rtOpts->driftFile); + } + } else if (fscanf(driftFP, "%lf", &recovered_drift) != 1) { + PERROR("Could not load saved offset from drift file - using current kernel frequency offset"); + fclose(driftFP); + } else { + + if(recovered_drift == 0) + recovered_drift = 0; + + fclose(driftFP); + if(quiet) + DBGV("Observed drift loaded from %s: "DRIFTFORMAT" ppb\n", + rtOpts->driftFile, + recovered_drift); + else + INFO("Observed drift loaded from %s: "DRIFTFORMAT" ppb\n", + rtOpts->driftFile, + recovered_drift); + break; + } + + case DRIFT_KERNEL: +#ifdef HAVE_SYS_TIMEX_H + recovered_drift = -getAdjFreq(); +#else + recovered_drift = 0; +#endif /* HAVE_SYS_TIMEX_H */ + if(recovered_drift == 0) + recovered_drift = 0; + + if (quiet) + DBGV("Observed_drift loaded from kernel: "DRIFTFORMAT" ppb\n", + recovered_drift); + else + INFO("Observed_drift loaded from kernel: "DRIFTFORMAT" ppb\n", + recovered_drift); + + break; + + + default: + reset_offset = TRUE; + break; + + } + + if (reset_offset) { + if (!rtOpts->noAdjust && ptpClock->clockControl.granted) + adjFreq_wrapper(rtOpts, ptpClock, 0); + ptpClock->servo.observedDrift = 0; + return; + } + + ptpClock->servo.observedDrift = recovered_drift; + + ptpClock->drift_saved = TRUE; + ptpClock->last_saved_drift = recovered_drift; + + if (!rtOpts->noAdjust) + adjFreq_wrapper(rtOpts, ptpClock, -recovered_drift); + +} + + + +void +saveDrift(PtpClock * ptpClock, const RunTimeOpts * rtOpts, Boolean quiet) +{ + FILE *driftFP; + + DBGV("saveDrift called\n"); + + if(ptpClock->portDS.portState == PTP_PASSIVE || + ptpClock->portDS.portState == PTP_MASTER || + ptpClock->defaultDS.clockQuality.clockClass < 128) { + DBGV("We're not slave - not saving drift\n"); + return; + } + + if(ptpClock->servo.observedDrift == 0.0 && + ptpClock->portDS.portState == PTP_LISTENING ) + return; + + if (rtOpts->drift_recovery_method > 0) { + ptpClock->last_saved_drift = ptpClock->servo.observedDrift; + ptpClock->drift_saved = TRUE; + } + + if (rtOpts->drift_recovery_method != DRIFT_FILE) + return; + + if(ptpClock->servo.runningMaxOutput) { + DBG("Servo running at maximum shift - not saving drift file"); + return; + } + + if( (driftFP = fopen(rtOpts->driftFile,"w")) == NULL) { + PERROR("Could not open drift file %s for writing", rtOpts->driftFile); + return; + } + + /* The fractional part really won't make a difference here */ + fprintf(driftFP, "%d\n", (int)round(ptpClock->servo.observedDrift)); + + if (quiet) { + DBGV("Wrote observed drift ("DRIFTFORMAT" ppb) to %s\n", + ptpClock->servo.observedDrift, rtOpts->driftFile); + } else { + INFO("Wrote observed drift ("DRIFTFORMAT" ppb) to %s\n", + ptpClock->servo.observedDrift, rtOpts->driftFile); + } + fclose(driftFP); +} + +#undef DRIFTFORMAT + +int parseLeapFile(char *path, LeapSecondInfo *info) +{ + FILE *leapFP; + TimeInternal now; + char lineBuf[PATH_MAX]; + + unsigned long ntpSeconds = 0; + Integer32 utcSeconds = 0; + Integer32 utcExpiry = 0; + int ntpOffset = 0; + int res; + + getTime(&now); + + info->valid = FALSE; + + if( (leapFP = fopen(path,"r")) == NULL) { + PERROR("Could not open leap second list file %s", path); + return 0; + } else + + memset(info, 0, sizeof(LeapSecondInfo)); + + while (fgets(lineBuf, PATH_MAX - 1, leapFP) != NULL) { + + /* capture file expiry time */ + res = sscanf(lineBuf, "#@ %lu", &ntpSeconds); + if(res == 1) { + utcExpiry = ntpSeconds - NTP_EPOCH; + DBG("leapfile expiry %d\n", utcExpiry); + } + /* capture leap seconds information */ + res = sscanf(lineBuf, "%lu %d", &ntpSeconds, &ntpOffset); + if(res ==2) { + utcSeconds = ntpSeconds - NTP_EPOCH; + DBG("leapfile date %d offset %d\n", utcSeconds, ntpOffset); + + /* next leap second date found */ + + if((now.seconds ) < utcSeconds) { + info->nextOffset = ntpOffset; + info->endTime = utcSeconds; + info->startTime = utcSeconds - 86400; + break; + } else + /* current leap second value found */ + if(now.seconds >= utcSeconds) { + info->currentOffset = ntpOffset; + } + + } + + } + + fclose(leapFP); + + /* leap file past expiry date */ + if(utcExpiry && utcExpiry < now.seconds) { + WARNING("Leap seconds file is expired. Please download the current version\n"); + return 0; + } + + /* we have the current offset - the rest can be invalid but at least we have this */ + if(info->currentOffset != 0) { + info->offsetValid = TRUE; + } + + /* if anything failed, return 0 so we know we cannot use leap file information */ + if((info->startTime == 0) || (info->endTime == 0) || + (info->currentOffset == 0) || (info->nextOffset == 0)) { + return 0; + INFO("Leap seconds file %s loaded (incomplete): now %d, current %d next %d from %d to %d, type %s\n", path, + now.seconds, + info->currentOffset, info->nextOffset, + info->startTime, info->endTime, info->leapType > 0 ? "positive" : info->leapType < 0 ? "negative" : "unknown"); + } + + if(info->nextOffset > info->currentOffset) { + info->leapType = 1; + } + + if(info->nextOffset < info->currentOffset) { + info->leapType = -1; + } + + INFO("Leap seconds file %s loaded: now %d, current %d next %d from %d to %d, type %s\n", path, + now.seconds, + info->currentOffset, info->nextOffset, + info->startTime, info->endTime, info->leapType > 0 ? "positive" : info->leapType < 0 ? "negative" : "unknown"); + info->valid = TRUE; + return 1; + +} + +void +updateXtmp (TimeInternal oldTime, TimeInternal newTime) +{ + +/* Add the old time entry to utmp/wtmp */ + +/* About as long as the ntpd implementation, but not any less ugly */ + +#ifdef HAVE_UTMPX_H + struct utmpx utx; + memset(&utx, 0, sizeof(utx)); + strncpy(utx.ut_user, "date", sizeof(utx.ut_user)); +#ifndef OTIME_MSG + strncpy(utx.ut_line, "|", sizeof(utx.ut_line)); +#else + strncpy(utx.ut_line, OTIME_MSG, sizeof(utx.ut_line)); +#endif /* OTIME_MSG */ +#ifdef OLD_TIME + utx.ut_tv.tv_sec = oldTime.seconds; + utx.ut_tv.tv_usec = oldTime.nanoseconds / 1000; + utx.ut_type = OLD_TIME; +#else /* no ut_type */ + utx.ut_time = oldTime.seconds; +#endif /* OLD_TIME */ + +/* ======== BEGIN OLD TIME EVENT - UTMPX / WTMPX =========== */ +#ifdef HAVE_UTMPXNAME + utmpxname("/var/log/utmp"); +#endif /* HAVE_UTMPXNAME */ + setutxent(); + pututxline(&utx); + endutxent(); +#ifdef HAVE_UPDWTMPX + updwtmpx("/var/log/wtmp", &utx); +#endif /* HAVE_IPDWTMPX */ +/* ======== END OLD TIME EVENT - UTMPX / WTMPX =========== */ + +#else /* NO UTMPX_H */ + +#ifdef HAVE_UTMP_H + struct utmp ut; + memset(&ut, 0, sizeof(ut)); + strncpy(ut.ut_name, "date", sizeof(ut.ut_name)); +#ifndef OTIME_MSG + strncpy(ut.ut_line, "|", sizeof(ut.ut_line)); +#else + strncpy(ut.ut_line, OTIME_MSG, sizeof(ut.ut_line)); +#endif /* OTIME_MSG */ + +#ifdef OLD_TIME + +#ifdef HAVE_STRUCT_UTMP_UT_TIME + ut.ut_time = oldTime.seconds; +#else + ut.ut_tv.tv_sec = oldTime.seconds; + ut.ut_tv.tv_usec = oldTime.nanoseconds / 1000; +#endif /* HAVE_STRUCT_UTMP_UT_TIME */ + + ut.ut_type = OLD_TIME; +#else /* no ut_type */ + ut.ut_time = oldTime.seconds; +#endif /* OLD_TIME */ + +/* ======== BEGIN OLD TIME EVENT - UTMP / WTMP =========== */ +#ifdef HAVE_UTMPNAME + utmpname(UTMP_FILE); +#endif /* HAVE_UTMPNAME */ +#ifdef HAVE_SETUTENT + setutent(); +#endif /* HAVE_SETUTENT */ +#ifdef HAVE_PUTUTLINE + pututline(&ut); +#endif /* HAVE_PUTUTLINE */ +#ifdef HAVE_ENDUTENT + endutent(); +#endif /* HAVE_ENDUTENT */ +#ifdef HAVE_UTMPNAME + utmpname(WTMP_FILE); +#endif /* HAVE_UTMPNAME */ +#ifdef HAVE_SETUTENT + setutent(); +#endif /* HAVE_SETUTENT */ +#ifdef HAVE_PUTUTLINE + pututline(&ut); +#endif /* HAVE_PUTUTLINE */ +#ifdef HAVE_ENDUTENT + endutent(); +#endif /* HAVE_ENDUTENT */ +/* ======== END OLD TIME EVENT - UTMP / WTMP =========== */ + +#endif /* HAVE_UTMP_H */ +#endif /* HAVE_UTMPX_H */ + +/* Add the new time entry to utmp/wtmp */ + +#ifdef HAVE_UTMPX_H + memset(&utx, 0, sizeof(utx)); + strncpy(utx.ut_user, "date", sizeof(utx.ut_user)); +#ifndef NTIME_MSG + strncpy(utx.ut_line, "{", sizeof(utx.ut_line)); +#else + strncpy(utx.ut_line, NTIME_MSG, sizeof(utx.ut_line)); +#endif /* NTIME_MSG */ +#ifdef NEW_TIME + utx.ut_tv.tv_sec = newTime.seconds; + utx.ut_tv.tv_usec = newTime.nanoseconds / 1000; + utx.ut_type = NEW_TIME; +#else /* no ut_type */ + utx.ut_time = newTime.seconds; +#endif /* NEW_TIME */ + +/* ======== BEGIN NEW TIME EVENT - UTMPX / WTMPX =========== */ +#ifdef HAVE_UTMPXNAME + utmpxname("/var/log/utmp"); +#endif /* HAVE_UTMPXNAME */ + setutxent(); + pututxline(&utx); + endutxent(); +#ifdef HAVE_UPDWTMPX + updwtmpx("/var/log/wtmp", &utx); +#endif /* HAVE_UPDWTMPX */ +/* ======== END NEW TIME EVENT - UTMPX / WTMPX =========== */ + +#else /* NO UTMPX_H */ + +#ifdef HAVE_UTMP_H + memset(&ut, 0, sizeof(ut)); + strncpy(ut.ut_name, "date", sizeof(ut.ut_name)); +#ifndef NTIME_MSG + strncpy(ut.ut_line, "{", sizeof(ut.ut_line)); +#else + strncpy(ut.ut_line, NTIME_MSG, sizeof(ut.ut_line)); +#endif /* NTIME_MSG */ +#ifdef NEW_TIME + +#ifdef HAVE_STRUCT_UTMP_UT_TIME + ut.ut_time = newTime.seconds; +#else + ut.ut_tv.tv_sec = newTime.seconds; + ut.ut_tv.tv_usec = newTime.nanoseconds / 1000; +#endif /* HAVE_STRUCT_UTMP_UT_TIME */ + ut.ut_type = NEW_TIME; +#else /* no ut_type */ + ut.ut_time = newTime.seconds; +#endif /* NEW_TIME */ + +/* ======== BEGIN NEW TIME EVENT - UTMP / WTMP =========== */ +#ifdef HAVE_UTMPNAME + utmpname(UTMP_FILE); +#endif /* HAVE_UTMPNAME */ +#ifdef HAVE_SETUTENT + setutent(); +#endif /* HAVE_SETUTENT */ +#ifdef HAVE_PUTUTLINE + pututline(&ut); +#endif /* HAVE_PUTUTLINE */ +#ifdef HAVE_ENDUTENT + endutent(); +#endif /* HAVE_ENDUTENT */ +#ifdef HAVE_UTMPNAME + utmpname(WTMP_FILE); +#endif /* HAVE_UTMPNAME */ +#ifdef HAVE_SETUTENT + setutent(); +#endif /* HAVE_SETUTENT */ +#ifdef HAVE_PUTUTLINE + pututline(&ut); +#endif /* HAVE_PUTUTLINE */ +#ifdef HAVE_ENDUTENT + endutent(); +#endif /* HAVE_ENDUTENT */ +/* ======== END NEW TIME EVENT - UTMP / WTMP =========== */ + +#endif /* HAVE_UTMP_H */ +#endif /* HAVE_UTMPX_H */ + +} + +int setCpuAffinity(int cpu) { + +#ifdef __QNXNTO__ + unsigned num_elements = 0; + int *rsizep, masksize_bytes, size; + int *rmaskp, *imaskp; + void *my_data; + uint32_t cpun; + num_elements = RMSK_SIZE(_syspage_ptr->num_cpu); + + masksize_bytes = num_elements * sizeof(unsigned); + + size = sizeof(int) + 2 * masksize_bytes; + if ((my_data = malloc(size)) == NULL) { + return -1; + } else { + memset(my_data, 0x00, size); + + rsizep = (int *)my_data; + rmaskp = rsizep + 1; + imaskp = rmaskp + num_elements; + + *rsizep = num_elements; + + if(cpu > _syspage_ptr->num_cpu) { + return -1; + } + + if(cpu >= 0) { + cpun = (uint32_t)cpu; + RMSK_SET(cpun, rmaskp); + RMSK_SET(cpun, imaskp); + } else { + for(cpun = 0; cpun < num_elements; cpun++) { + RMSK_SET(cpun, rmaskp); + RMSK_SET(cpun, imaskp); + } + } + int ret = ThreadCtl( _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT, my_data); + free(my_data); + return ret; + } + +#endif + +#ifdef HAVE_SYS_CPUSET_H + cpuset_t mask; + CPU_ZERO(&mask); + if(cpu >= 0) { + CPU_SET(cpu,&mask); + } else { + int i; + for(i = 0; i < CPU_SETSIZE; i++) { + CPU_SET(i, &mask); + } + } + return(cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, + -1, sizeof(mask), &mask)); +#endif /* HAVE_SYS_CPUSET_H */ + +#if defined(linux) && defined(HAVE_SCHED_H) + cpu_set_t mask; + CPU_ZERO(&mask); + if(cpu >= 0) { + CPU_SET(cpu,&mask); + } else { + int i; + for(i = 0; i < CPU_SETSIZE; i++) { + CPU_SET(i, &mask); + } + } + return sched_setaffinity(0, sizeof(mask), &mask); +#endif /* linux && HAVE_SCHED_H */ + +return -1; + +} diff --git a/src/ptpd/src/display.c b/src/ptpd/src/display.c new file mode 100644 index 0000000..6c9d525 --- /dev/null +++ b/src/ptpd/src/display.c @@ -0,0 +1,1152 @@ +/*- + * Copyright (c) 2012-2013 George V. Neville-Neil, + * Wojciech Owczarek + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file display.c + * @date Thu Aug 12 09:06:21 2010 + * + * @brief General routines for displaying internal data. + * + * + */ + +#include "ptpd.h" + +/**\brief Display an Integer64 type*/ +void +integer64_display(const Integer64 * bigint) +{ + DBGV("Integer 64 : \n"); + DBGV("LSB : %u\n", bigint->lsb); + DBGV("MSB : %d\n", bigint->msb); +} + +/**\brief Display an UInteger48 type*/ +void +uInteger48_display(const UInteger48 * bigint) +{ + DBGV("Integer 48 : \n"); + DBGV("LSB : %u\n", bigint->lsb); + DBGV("MSB : %u\n", bigint->msb); +} + +/** \brief Display a TimeInternal Structure*/ +void +timeInternal_display(const TimeInternal * timeInternal) +{ + DBGV("seconds : %d \n", timeInternal->seconds); + DBGV("nanoseconds %d \n", timeInternal->nanoseconds); +} + +/** \brief Display a Timestamp Structure*/ +void +timestamp_display(const Timestamp * timestamp) +{ + uInteger48_display(×tamp->secondsField); + DBGV("nanoseconds %u \n", timestamp->nanosecondsField); +} + +/**\brief Display a Clockidentity Structure*/ +void +clockIdentity_display(const ClockIdentity clockIdentity) +{ + DBGV( + "ClockIdentity : %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + clockIdentity[0], clockIdentity[1], clockIdentity[2], + clockIdentity[3], clockIdentity[4], clockIdentity[5], + clockIdentity[6], clockIdentity[7] + ); +} + +/**\brief Display MAC address*/ +void +clockUUID_display(const Octet * sourceUuid) +{ + DBGV( + "sourceUuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + sourceUuid[0], sourceUuid[1], sourceUuid[2], sourceUuid[3], + sourceUuid[4], sourceUuid[5] + ); +} + +/**\brief Display Network info*/ +void +netPath_display(const NetPath * net) +{ +#ifdef RUNTIME_DEBUG + struct in_addr tmpAddr; + DBGV("eventSock : %d \n", net->eventSock); + DBGV("generalSock : %d \n", net->generalSock); + tmpAddr.s_addr = net->multicastAddr; + DBGV("multicastAdress : %s \n", inet_ntoa(tmpAddr)); + tmpAddr.s_addr = net->peerMulticastAddr; + DBGV("peerMulticastAddress : %s \n", inet_ntoa(tmpAddr)); +#endif /* RUNTIME_DEBUG */ +} + +/**\brief Display a IntervalTimer Structure*/ +void +intervalTimer_display(const IntervalTimer * ptimer) +{ + DBGV("interval : %.06f \n", ptimer->interval); + DBGV("expire : %d \n", ptimer->expired); +} + +/**\brief Display a TimeInterval Structure*/ +void +timeInterval_display(const TimeInterval * timeInterval) +{ + integer64_display(&timeInterval->scaledNanoseconds); +} + +/**\brief Display a Portidentity Structure*/ +void +portIdentity_display(const PortIdentity * portIdentity) +{ + clockIdentity_display(portIdentity->clockIdentity); + DBGV("port number : %d \n", portIdentity->portNumber); +} + +/**\brief Display a Clockquality Structure*/ +void +clockQuality_display(const ClockQuality * clockQuality) +{ + DBGV("clockClass : %d \n", clockQuality->clockClass); + DBGV("clockAccuracy : %d \n", clockQuality->clockAccuracy); + DBGV("offsetScaledLogVariance : %d \n", clockQuality->offsetScaledLogVariance); +} + +/**\brief Display PTPText Structure*/ +void +PTPText_display(const PTPText *p, const PtpClock *ptpClock) +{ + DBGV(" lengthField : %d \n", p->lengthField); + DBGV(" textField : %.*s \n", (int)p->lengthField, p->textField); +} + +/**\brief Display the Network Interface Name*/ +void +iFaceName_display(const Octet * iFaceName) +{ + + int i; + + DBGV("iFaceName : "); + + for (i = 0; i < IFACE_NAME_LENGTH; i++) { + DBGV("%c", iFaceName[i]); + } + DBGV("\n"); + +} + +/**\brief Display an Unicast Adress*/ +void +unicast_display(const Octet * unicast) +{ + + int i; + + DBGV("Unicast adress : "); + + for (i = 0; i < NET_ADDRESS_LENGTH; i++) { + DBGV("%c", unicast[i]); + } + DBGV("\n"); + +} + + +/**\brief Display Sync message*/ +void +msgSync_display(const MsgSync * sync) +{ + DBGV("Message Sync : \n"); + timestamp_display(&sync->originTimestamp); + DBGV("\n"); +} + +/**\brief Display Header message*/ +void +msgHeader_display(const MsgHeader * header) +{ + DBGV("Message header : \n"); + DBGV("\n"); + DBGV("transportSpecific : %d\n", header->transportSpecific); + DBGV("messageType : %d\n", header->messageType); + DBGV("versionPTP : %d\n", header->versionPTP); + DBGV("messageLength : %d\n", header->messageLength); + DBGV("domainNumber : %d\n", header->domainNumber); + DBGV("FlagField %02hhx:%02hhx\n", header->flagField0, header->flagField1); + DBGV("CorrectionField : \n"); + integer64_display(&header->correctionField); + DBGV("SourcePortIdentity : \n"); + portIdentity_display(&header->sourcePortIdentity); + DBGV("sequenceId : %d\n", header->sequenceId); + DBGV("controlField : %d\n", header->controlField); + DBGV("logMessageInterval : %d\n", header->logMessageInterval); + DBGV("\n"); +} + +/**\brief Display Announce message*/ +void +msgAnnounce_display(const MsgAnnounce * announce) +{ + DBGV("Announce Message : \n"); + DBGV("\n"); + DBGV("originTimestamp : \n"); + DBGV("secondField : \n"); + timestamp_display(&announce->originTimestamp); + DBGV("currentUtcOffset : %d \n", announce->currentUtcOffset); + DBGV("grandMasterPriority1 : %d \n", announce->grandmasterPriority1); + DBGV("grandMasterClockQuality : \n"); + clockQuality_display(&announce->grandmasterClockQuality); + DBGV("grandMasterPriority2 : %d \n", announce->grandmasterPriority2); + DBGV("grandMasterIdentity : \n"); + clockIdentity_display(announce->grandmasterIdentity); + DBGV("stepsRemoved : %d \n", announce->stepsRemoved); + DBGV("timeSource : %d \n", announce->timeSource); + DBGV("\n"); +} + +/**\brief Display Follow_UP message*/ +void +msgFollowUp_display(const MsgFollowUp * follow) +{ + timestamp_display(&follow->preciseOriginTimestamp); +} + +/**\brief Display DelayReq message*/ +void +msgDelayReq_display(const MsgDelayReq * req) +{ + timestamp_display(&req->originTimestamp); +} + +/**\brief Display DelayResp message*/ +void +msgDelayResp_display(const MsgDelayResp * resp) +{ + timestamp_display(&resp->receiveTimestamp); + portIdentity_display(&resp->requestingPortIdentity); +} + +/**\brief Display Pdelay_Req message*/ +void +msgPdelayReq_display(const MsgPdelayReq * preq) +{ + timestamp_display(&preq->originTimestamp); +} + +/**\brief Display Pdelay_Resp message*/ +void +msgPdelayResp_display(const MsgPdelayResp * presp) +{ + + timestamp_display(&presp->requestReceiptTimestamp); + portIdentity_display(&presp->requestingPortIdentity); +} + +/**\brief Display Pdelay_Resp Follow Up message*/ +void +msgPdelayRespFollowUp_display(const MsgPdelayRespFollowUp * prespfollow) +{ + + timestamp_display(&prespfollow->responseOriginTimestamp); + portIdentity_display(&prespfollow->requestingPortIdentity); +} + +/**\brief Display Management message*/ +void +msgManagement_display(const MsgManagement * manage) +{ + DBGV("Management Message : \n"); + DBGV("\n"); + DBGV("targetPortIdentity : \n"); + portIdentity_display(&manage->targetPortIdentity); + DBGV("startingBoundaryHops : %d \n", manage->startingBoundaryHops); + DBGV("boundaryHops : %d \n", manage->boundaryHops); + DBGV("actionField : %d\n", manage->actionField); +} + +/**\brief Display ManagementTLV Slave Only message*/ +void +mMSlaveOnly_display(const MMSlaveOnly *slaveOnly, const PtpClock *ptpClock) +{ + DBGV("Slave Only ManagementTLV message \n"); + DBGV("SO : %d \n", slaveOnly->so); +} + +/**\brief Display ManagementTLV Clock Description message*/ +void +mMClockDescription_display(const MMClockDescription *clockDescription, const PtpClock *ptpClock) +{ + DBGV("Clock Description ManagementTLV message \n"); + DBGV("clockType0 : %d \n", clockDescription->clockType0); + DBGV("clockType1 : %d \n", clockDescription->clockType1); + DBGV("physicalLayerProtocol : \n"); + PTPText_display(&clockDescription->physicalLayerProtocol, ptpClock); + DBGV("physicalAddressLength : %d \n", clockDescription->physicalAddress.addressLength); + if(clockDescription->physicalAddress.addressField) { + DBGV("physicalAddressField : \n"); + clockUUID_display(clockDescription->physicalAddress.addressField); + } + DBGV("protocolAddressNetworkProtocol : %d \n", clockDescription->protocolAddress.networkProtocol); + DBGV("protocolAddressLength : %d \n", clockDescription->protocolAddress.addressLength); + if(clockDescription->protocolAddress.addressField) { + DBGV("protocolAddressField : %d.%d.%d.%d \n", + (UInteger8)clockDescription->protocolAddress.addressField[0], + (UInteger8)clockDescription->protocolAddress.addressField[1], + (UInteger8)clockDescription->protocolAddress.addressField[2], + (UInteger8)clockDescription->protocolAddress.addressField[3]); + } + DBGV("manufacturerIdentity0 : %d \n", clockDescription->manufacturerIdentity0); + DBGV("manufacturerIdentity1 : %d \n", clockDescription->manufacturerIdentity1); + DBGV("manufacturerIdentity2 : %d \n", clockDescription->manufacturerIdentity2); + DBGV("productDescription : \n"); + PTPText_display(&clockDescription->productDescription, ptpClock); + DBGV("revisionData : \n"); + PTPText_display(&clockDescription->revisionData, ptpClock); + DBGV("userDescription : \n"); + PTPText_display(&clockDescription->userDescription, ptpClock); + DBGV("profileIdentity0 : %d \n", clockDescription->profileIdentity0); + DBGV("profileIdentity1 : %d \n", clockDescription->profileIdentity1); + DBGV("profileIdentity2 : %d \n", clockDescription->profileIdentity2); + DBGV("profileIdentity3 : %d \n", clockDescription->profileIdentity3); + DBGV("profileIdentity4 : %d \n", clockDescription->profileIdentity4); + DBGV("profileIdentity5 : %d \n", clockDescription->profileIdentity5); +} + +void +mMUserDescription_display(const MMUserDescription* userDescription, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMInitialize_display(const MMInitialize* initialize, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMDefaultDataSet_display(const MMDefaultDataSet* defaultDataSet, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMCurrentDataSet_display(const MMCurrentDataSet* currentDataSet, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMParentDataSet_display(const MMParentDataSet* parentDataSet, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMTimePropertiesDataSet_display(const MMTimePropertiesDataSet* timePropertiesDataSet, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMPortDataSet_display(const MMPortDataSet* portDataSet, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMPriority1_display(const MMPriority1* priority1, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMPriority2_display(const MMPriority2* priority2, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMDomain_display(const MMDomain* domain, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMLogAnnounceInterval_display(const MMLogAnnounceInterval* logAnnounceInterval, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMAnnounceReceiptTimeout_display(const MMAnnounceReceiptTimeout* announceReceiptTimeout, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMLogSyncInterval_display(const MMLogSyncInterval* logSyncInterval, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMVersionNumber_display(const MMVersionNumber* versionNumber, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMTime_display(const MMTime* time, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMClockAccuracy_display(const MMClockAccuracy* clockAccuracy, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMUtcProperties_display(const MMUtcProperties* utcProperties, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMTraceabilityProperties_display(const MMTraceabilityProperties* traceabilityProperties, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMTimescaleProperties_display(const MMTimescaleProperties* TimescaleProperties, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMUnicastNegotiationEnable_display(const MMUnicastNegotiationEnable* unicastNegotiationEnable, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + + +void +mMDelayMechanism_display(const MMDelayMechanism* delayMechanism, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMLogMinPdelayReqInterval_display(const MMLogMinPdelayReqInterval* logMinPdelayReqInterval, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +void +mMErrorStatus_display(const MMErrorStatus* errorStatus, const PtpClock *ptpClock) +{ + /* TODO: implement me */ +} + +/**\brief Display Signaling message*/ +void +msgSignaling_display(const MsgSignaling * signaling) +{ + DBGV("Signaling Message : \n"); + DBGV("\n"); + DBGV("targetPortIdentity : \n"); + portIdentity_display(&signaling->targetPortIdentity); +} + +void +sMRequestUnicastTransmission_display(const SMRequestUnicastTransmission* request, const PtpClock *ptpClock) +{ + DBGV("Request Unicast Transmission SignalingTLV message \n"); + DBGV("messageType: %d\n", request->messageType); + DBGV("logInterMessagePeriod: %d\n", request->logInterMessagePeriod); + DBGV("durationField: %d\n", request->durationField); +} + +void +sMGrantUnicastTransmission_display(const SMGrantUnicastTransmission* grant, const PtpClock *ptpClock) +{ + DBGV("Grant Unicast Transmission SignalingTLV message \n"); + DBGV("messageType: %d\n", grant->messageType); + DBGV("logInterMessagePeriod: %d\n", grant->logInterMessagePeriod); + DBGV("durationField: %d\n", grant->durationField); + DBGV("R (renewal invited): %d\n", grant->renewal_invited); +} + +void +sMCancelUnicastTransmission_display(const SMCancelUnicastTransmission* tlv, const PtpClock *ptpClock) +{ + DBGV("Cancel Unicast Transmission SignalingTLV message \n"); + DBGV("messageType: %d\n", tlv->messageType); +} + +void +sMAcknowledgeCancelUnicastTransmission_display(const SMAcknowledgeCancelUnicastTransmission* tlv, const PtpClock *ptpClock) +{ + DBGV("Acknowledge Cancel Unicast Transmission SignalingTLV message \n"); + DBGV("messageType: %d\n", tlv->messageType); +} + +#define FORMAT_SERVO "%f" + +/**\brief Display runTimeOptions structure*/ +void +displayRunTimeOpts(const RunTimeOpts * rtOpts) +{ + + DBGV("---Run time Options Display-- \n"); + DBGV("\n"); + DBGV("announceInterval : %d \n", rtOpts->logAnnounceInterval); + DBGV("syncInterval : %d \n", rtOpts->logSyncInterval); + clockQuality_display(&(rtOpts->clockQuality)); + DBGV("priority1 : %d \n", rtOpts->priority1); + DBGV("priority2 : %d \n", rtOpts->priority2); + DBGV("domainNumber : %d \n", rtOpts->domainNumber); + DBGV("slaveOnly : %d \n", rtOpts->slaveOnly); + DBGV("currentUtcOffset : %d \n", rtOpts->timeProperties.currentUtcOffset); + DBGV("noAdjust : %d \n", rtOpts->noAdjust); + DBGV("logStatistics : %d \n", rtOpts->logStatistics); + iFaceName_display(rtOpts->ifaceName); + DBGV("kP : %d \n", rtOpts->servoKP); + DBGV("kI : %d \n", rtOpts->servoKI); + DBGV("s : %d \n", rtOpts->s); + DBGV("inbound latency : \n"); + timeInternal_display(&(rtOpts->inboundLatency)); + DBGV("outbound latency : \n"); + timeInternal_display(&(rtOpts->outboundLatency)); + DBGV("max_foreign_records : %d \n", rtOpts->max_foreign_records); + DBGV("transport : %d \n", rtOpts->transport); + DBGV("\n"); +} + + +/**\brief Display Default data set of a PtpClock*/ +void +displayDefault(const PtpClock * ptpClock) +{ + DBGV("---Ptp Clock Default Data Set-- \n"); + DBGV("\n"); + DBGV("twoStepFlag : %d \n", ptpClock->defaultDS.twoStepFlag); + clockIdentity_display(ptpClock->defaultDS.clockIdentity); + DBGV("numberPorts : %d \n", ptpClock->defaultDS.numberPorts); + clockQuality_display(&(ptpClock->defaultDS.clockQuality)); + DBGV("priority1 : %d \n", ptpClock->defaultDS.priority1); + DBGV("priority2 : %d \n", ptpClock->defaultDS.priority2); + DBGV("domainNumber : %d \n", ptpClock->defaultDS.domainNumber); + DBGV("slaveOnly : %d \n", ptpClock->defaultDS.slaveOnly); + DBGV("\n"); +} + + +/**\brief Display Current data set of a PtpClock*/ +void +displayCurrent(const PtpClock * ptpClock) +{ + DBGV("---Ptp Clock Current Data Set-- \n"); + DBGV("\n"); + + DBGV("stepsremoved : %d \n", ptpClock->currentDS.stepsRemoved); + DBGV("Offset from master : \n"); + timeInternal_display(&ptpClock->currentDS.offsetFromMaster); + DBGV("Mean path delay : \n"); + timeInternal_display(&ptpClock->currentDS.meanPathDelay); + DBGV("\n"); +} + + + +/**\brief Display Parent data set of a PtpClock*/ +void +displayParent(const PtpClock * ptpClock) +{ + DBGV("---Ptp Clock Parent Data Set-- \n"); + DBGV("\n"); + portIdentity_display(&(ptpClock->parentDS.parentPortIdentity)); + DBGV("parentStats : %d \n", ptpClock->parentDS.parentStats); + DBGV("observedParentOffsetScaledLogVariance : %d \n", ptpClock->parentDS.observedParentOffsetScaledLogVariance); + DBGV("observedParentClockPhaseChangeRate : %d \n", ptpClock->parentDS.observedParentClockPhaseChangeRate); + DBGV("--GrandMaster--\n"); + clockIdentity_display(ptpClock->parentDS.grandmasterIdentity); + clockQuality_display(&ptpClock->parentDS.grandmasterClockQuality); + DBGV("grandmasterpriority1 : %d \n", ptpClock->parentDS.grandmasterPriority1); + DBGV("grandmasterpriority2 : %d \n", ptpClock->parentDS.grandmasterPriority2); + DBGV("\n"); +} + +/**\brief Display Global data set of a PtpClock*/ +void +displayGlobal(const PtpClock * ptpClock) +{ + DBGV("---Ptp Clock Global Time Data Set-- \n"); + DBGV("\n"); + + DBGV("currentUtcOffset : %d \n", ptpClock->timePropertiesDS.currentUtcOffset); + DBGV("currentUtcOffsetValid : %d \n", ptpClock->timePropertiesDS.currentUtcOffsetValid); + DBGV("leap59 : %d \n", ptpClock->timePropertiesDS.leap59); + DBGV("leap61 : %d \n", ptpClock->timePropertiesDS.leap61); + DBGV("timeTraceable : %d \n", ptpClock->timePropertiesDS.timeTraceable); + DBGV("frequencyTraceable : %d \n", ptpClock->timePropertiesDS.frequencyTraceable); + DBGV("ptpTimescale : %d \n", ptpClock->timePropertiesDS.ptpTimescale); + DBGV("timeSource : %d \n", ptpClock->timePropertiesDS.timeSource); + DBGV("\n"); +} + +/**\brief Display Port data set of a PtpClock*/ +void +displayPort(const PtpClock * ptpClock) +{ + DBGV("---Ptp Clock Port Data Set-- \n"); + DBGV("\n"); + + portIdentity_display(&ptpClock->portDS.portIdentity); + DBGV("port state : %d \n", ptpClock->portDS.portState); + DBGV("logMinDelayReqInterval : %d \n", ptpClock->portDS.logMinDelayReqInterval); + DBGV("peerMeanPathDelay : \n"); + timeInternal_display(&ptpClock->portDS.peerMeanPathDelay); + DBGV("logAnnounceInterval : %d \n", ptpClock->portDS.logAnnounceInterval); + DBGV("announceReceiptTimeout : %d \n", ptpClock->portDS.announceReceiptTimeout); + DBGV("logSyncInterval : %d \n", ptpClock->portDS.logSyncInterval); + DBGV("delayMechanism : %d \n", ptpClock->portDS.delayMechanism); + DBGV("logMinPdelayReqInterval : %d \n", ptpClock->portDS.logMinPdelayReqInterval); + DBGV("versionNumber : %d \n", ptpClock->portDS.versionNumber); + DBGV("\n"); +} + +/**\brief Display ForeignMaster data set of a PtpClock*/ +void +displayForeignMaster(const PtpClock * ptpClock) +{ + + ForeignMasterRecord *foreign; + int i; + + if (ptpClock->number_foreign_records > 0) { + + DBGV("---Ptp Clock Foreign Data Set-- \n"); + DBGV("\n"); + DBGV("There is %d Foreign master Recorded \n", ptpClock->number_foreign_records); + foreign = ptpClock->foreign; + + for (i = 0; i < ptpClock->number_foreign_records; i++) { + + portIdentity_display(&foreign->foreignMasterPortIdentity); + DBGV("number of Announce message received : %d \n", foreign->foreignMasterAnnounceMessages); + msgHeader_display(&foreign->header); + msgAnnounce_display(&foreign->announce); + + foreign++; + } + + } else { + DBGV("No Foreign masters recorded \n"); + } + + DBGV("\n"); + + +} + +/**\brief Display other data set of a PtpClock*/ + +void +displayOthers(const PtpClock * ptpClock) +{ + + int i; + + /* Usefull to display name of timers */ +#ifdef PTPD_DBGV + char timer[][26] = { + "PDELAYREQ_INTERVAL_TIMER", + "SYNC_INTERVAL_TIMER", + "ANNOUNCE_RECEIPT_TIMER", + "ANNOUNCE_INTERVAL_TIMER" + }; +#endif + DBGV("---Ptp Others Data Set-- \n"); + DBGV("\n"); + + /*DBGV("master_to_slave_delay : \n"); + timeInternal_display(&ptpClock->master_to_slave_delay); + DBGV("\n"); + DBGV("slave_to_master_delay : \n"); + timeInternal_display(&ptpClock->slave_to_master_delay); + */ + + DBGV("\n"); + DBGV("delay_req_receive_time : \n"); + timeInternal_display(&ptpClock->pdelay_req_receive_time); + DBGV("\n"); + DBGV("delay_req_send_time : \n"); + timeInternal_display(&ptpClock->pdelay_req_send_time); + DBGV("\n"); + DBGV("delay_resp_receive_time : \n"); + timeInternal_display(&ptpClock->pdelay_resp_receive_time); + DBGV("\n"); + DBGV("delay_resp_send_time : \n"); + timeInternal_display(&ptpClock->pdelay_resp_send_time); + DBGV("\n"); + DBGV("sync_receive_time : \n"); + timeInternal_display(&ptpClock->sync_receive_time); + DBGV("\n"); + //DBGV("R : %f \n", ptpClock->R); + DBGV("sentPdelayReq : %d \n", ptpClock->sentPdelayReq); + DBGV("sentPdelayReqSequenceId : %d \n", ptpClock->sentPdelayReqSequenceId); + DBGV("waitingForFollow : %d \n", ptpClock->waitingForFollow); + DBGV("\n"); + DBGV("Offset from master filter : \n"); + DBGV("nsec_prev : %d \n", ptpClock->ofm_filt.nsec_prev); + DBGV("y : %d \n", ptpClock->ofm_filt.y); + DBGV("\n"); + DBGV("One way delay filter : \n"); + DBGV("nsec_prev : %d \n", ptpClock->mpd_filt.nsec_prev); + DBGV("y : %d \n", ptpClock->mpd_filt.y); + DBGV("s_exp : %d \n", ptpClock->mpd_filt.s_exp); + DBGV("\n"); + DBGV("observed drift : "FORMAT_SERVO" \n", ptpClock->servo.observedDrift); + DBGV("message activity %d \n", ptpClock->message_activity); + DBGV("\n"); + + for (i = 0; i < PTP_MAX_TIMER; i++) { + DBGV("%s : \n", timer[i]); + intervalTimer_display(&ptpClock->timers[i]); + DBGV("\n"); + } + + netPath_display(&ptpClock->netPath); + DBGV("mCommunication technology %d \n", ptpClock->port_communication_technology); + clockUUID_display(ptpClock->netPath.interfaceID); + DBGV("\n"); +} + + +/**\brief Display Buffer in & out of a PtpClock*/ +void +displayBuffer(const PtpClock * ptpClock) +{ + + int i; + int j; + + j = 0; + + DBGV("PtpClock Buffer Out \n"); + DBGV("\n"); + + for (i = 0; i < PACKET_SIZE; i++) { + DBGV(":%02hhx", ptpClock->msgObuf[i]); + j++; + + if (j == 8) { + DBGV(" "); + + } + if (j == 16) { + DBGV("\n"); + j = 0; + } + } + DBGV("\n"); + j = 0; + DBGV("\n"); + + DBGV("PtpClock Buffer In \n"); + DBGV("\n"); + for (i = 0; i < PACKET_SIZE; i++) { + DBGV(":%02hhx", ptpClock->msgIbuf[i]); + j++; + + if (j == 8) { + DBGV(" "); + + } + if (j == 16) { + DBGV("\n"); + j = 0; + } + } + DBGV("\n"); + DBGV("\n"); +} + +/**\convert port state to string*/ +const char +*portState_getName(Enumeration8 portState) +{ + static const char *ptpStates[] = { + [PTP_INITIALIZING] = "PTP_INITIALIZING", + [PTP_FAULTY] = "PTP_FAULTY", + [PTP_DISABLED] = "PTP_DISABLED", + [PTP_LISTENING] = "PTP_LISTENING", + [PTP_PRE_MASTER] = "PTP_PRE_MASTER", + [PTP_MASTER] = "PTP_MASTER", + [PTP_PASSIVE] = "PTP_PASSIVE", + [PTP_UNCALIBRATED] = "PTP_UNCALIBRATED", + [PTP_SLAVE] = "PTP_SLAVE" + }; + + /* converting to int to avoid compiler warnings when comparing enum*/ + static const int max = PTP_SLAVE; + int intstate = portState; + + if( intstate < 0 || intstate > max ) { + return("PTP_UNKNOWN"); + } + + return(ptpStates[portState]); + +} + +/**\brief Display all PTP clock (port) counters*/ +void +displayCounters(const PtpClock * ptpClock) +{ + + /* TODO: print port identity */ + INFO("\n============= PTP port counters =============\n"); + + INFO("Message counters:\n"); + INFO(" announceMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.announceMessagesSent); + INFO(" announceMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.announceMessagesReceived); + INFO(" syncMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.syncMessagesSent); + INFO(" syncMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.syncMessagesReceived); + INFO(" followUpMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.followUpMessagesSent); + INFO(" followUpMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.followUpMessagesReceived); + INFO(" delayReqMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.delayReqMessagesSent); + INFO(" delayReqMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.delayReqMessagesReceived); + INFO(" delayRespMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.delayRespMessagesSent); + INFO(" delayRespMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.delayRespMessagesReceived); + INFO(" pdelayReqMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.pdelayReqMessagesSent); + INFO(" pdelayReqMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.pdelayReqMessagesReceived); + INFO(" pdelayRespMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.pdelayRespMessagesSent); + INFO(" pdelayRespMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.pdelayRespMessagesReceived); + INFO(" pdelayRespFollowUpMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.pdelayRespFollowUpMessagesSent); + INFO("pdelayRespFollowUpMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.pdelayRespFollowUpMessagesReceived); + INFO(" signalingMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.signalingMessagesSent); + INFO(" signalingMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.signalingMessagesReceived); + INFO(" managementMessagesSent : %lu\n", + (unsigned long)ptpClock->counters.managementMessagesSent); + INFO(" managementMessagesReceived : %lu\n", + (unsigned long)ptpClock->counters.managementMessagesReceived); + + if(ptpClock->counters.signalingMessagesReceived || + ptpClock->counters.signalingMessagesSent) { + INFO("Unicast negotiation counters:\n"); + INFO(" unicastGrantsRequested : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsRequested); + INFO(" unicastGrantsGranted : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsGranted); + INFO(" unicastGrantsDenied : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsDenied); + INFO(" unicastGrantsCancelSent : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsCancelSent); + INFO(" unicastGrantsCancelReceived : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsCancelReceived); + INFO(" unicastGrantsCancelAckReceived : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsCancelAckReceived); + INFO(" unicastGrantsCancelAckSent : %lu\n", + (unsigned long)ptpClock->counters.unicastGrantsCancelAckSent); + + } +/* not implemented yet */ +#if 0 + INFO("FMR counters:\n"); + INFO(" foreignAdded : %lu\n", + (unsigned long)ptpClock->counters.foreignAdded); + INFO(" foreignMax : %lu\n", + (unsigned long)ptpClock->counters.foreignMax); + INFO(" foreignRemoved : %lu\n", + (unsigned long)ptpClock->counters.foreignRemoved); + INFO(" foreignOverflow : %lu\n", + (unsigned long)ptpClock->counters.foreignOverflow); +#endif /* 0 */ + + INFO("Protocol engine counters:\n"); + INFO(" stateTransitions : %lu\n", + (unsigned long)ptpClock->counters.stateTransitions); + INFO(" bestMasterChanges : %lu\n", + (unsigned long)ptpClock->counters.bestMasterChanges); + INFO(" announceTimeouts : %lu\n", + (unsigned long)ptpClock->counters.announceTimeouts); + + INFO("Discarded / unknown message counters:\n"); + INFO(" discardedMessages : %lu\n", + (unsigned long)ptpClock->counters.discardedMessages); + INFO(" unknownMessages : %lu\n", + (unsigned long)ptpClock->counters.unknownMessages); + INFO(" ignoredAnnounce : %lu\n", + (unsigned long)ptpClock->counters.ignoredAnnounce); + INFO(" aclManagementMessagesDiscarded : %lu\n", + (unsigned long)ptpClock->counters.aclManagementMessagesDiscarded); + INFO(" aclTimingMessagesDiscarded : %lu\n", + (unsigned long)ptpClock->counters.aclTimingMessagesDiscarded); + + INFO("Error counters:\n"); + INFO(" messageSendErrors : %lu\n", + (unsigned long)ptpClock->counters.messageSendErrors); + INFO(" messageRecvErrors : %lu\n", + (unsigned long)ptpClock->counters.messageRecvErrors); + INFO(" messageFormatErrors : %lu\n", + (unsigned long)ptpClock->counters.messageFormatErrors); + INFO(" protocolErrors : %lu\n", + (unsigned long)ptpClock->counters.protocolErrors); + INFO(" versionMismatchErrors : %lu\n", + (unsigned long)ptpClock->counters.versionMismatchErrors); + INFO(" domainMismatchErrors : %lu\n", + (unsigned long)ptpClock->counters.domainMismatchErrors); + INFO(" sequenceMismatchErrors : %lu\n", + (unsigned long)ptpClock->counters.sequenceMismatchErrors); + INFO(" consecutiveSequenceErrors : %lu\n", + (unsigned long)ptpClock->counters.consecutiveSequenceErrors); + INFO(" delayMechanismMismatchErrors : %lu\n", + (unsigned long)ptpClock->counters.delayMechanismMismatchErrors); + INFO(" maxDelayDrops : %lu\n", + (unsigned long)ptpClock->counters.maxDelayDrops); + + +#ifdef PTPD_STATISTICS + INFO("Outlier filter hits:\n"); + INFO(" delayMSOutliersFound : %lu\n", + (unsigned long)ptpClock->counters.delayMSOutliersFound); + INFO(" delaySMOutliersFound : %lu\n", + (unsigned long)ptpClock->counters.delaySMOutliersFound); +#endif /* PTPD_STATISTICS */ + +} + +const char * +getTimeSourceName(Enumeration8 timeSource) +{ + + switch(timeSource) { + + case ATOMIC_CLOCK: + return "ATOMIC_CLOCK"; + case GPS: + return "GPS"; + case TERRESTRIAL_RADIO: + return "TERRERSTRIAL_RADIO"; + case PTP: + return "PTP"; + case NTP: + return "NTP"; + case HAND_SET: + return "HAND_SET"; + case OTHER: + return "OTHER"; + case INTERNAL_OSCILLATOR: + return "INTERNAL_OSCILLATOR"; + default: + return "UNKNOWN"; + } + +} + +const char * +getMessageTypeName(Enumeration8 messageType) +{ + switch(messageType) + { + case ANNOUNCE: + return("Announce"); + break; + case SYNC: + return("Sync"); + break; + case FOLLOW_UP: + return("FollowUp"); + break; + case DELAY_REQ: + return("DelayReq"); + break; + case DELAY_RESP: + return("DelayResp"); + break; + case PDELAY_REQ: + return("PdelayReq"); + break; + case PDELAY_RESP: + return("PdelayResp"); + break; + case PDELAY_RESP_FOLLOW_UP: + return("PdelayRespFollowUp"); + break; + case MANAGEMENT: + return("Management"); + break; + case SIGNALING: + return("Signaling"); + break; + default: + return("Unknown"); + break; + } + +} + +const char* accToString(uint8_t acc) { + + switch(acc) { + + case ACC_25NS: + return "ACC_25NS"; + case ACC_100NS: + return "ACC_100NS"; + case ACC_250NS: + return "ACC_250NS"; + case ACC_1US: + return "ACC_1US"; + case ACC_2_5US: + return "ACC_2_5US"; + case ACC_10US: + return "ACC_10US"; + case ACC_25US: + return "ACC_25US"; + case ACC_100US: + return "ACC_100US"; + case ACC_250US: + return "ACC_250US"; + case ACC_1MS: + return "ACC_1MS"; + case ACC_2_5MS: + return "ACC_2_5MS"; + case ACC_10MS: + return "ACC_10MS"; + case ACC_25MS: + return "ACC_25MS"; + case ACC_100MS: + return "ACC_100MS"; + case ACC_250MS: + return "ACC_250MS"; + case ACC_1S: + return "ACC_1S"; + case ACC_10S: + return "ACC_10S"; + case ACC_10SPLUS: + return "ACC_10SPLUS"; + case ACC_UNKNOWN: + return "ACC_UNKNOWN"; + default: + return NULL; + + } +} + +const char* delayMechToString(uint8_t mech) { + + switch(mech) { + case E2E: + return "E2E"; + case P2P: + return "P2P"; + case DELAY_DISABLED: + return "DELAY_DISABLED"; + default: + return NULL; + } + +} + +/**\brief Display all PTP clock (port) statistics*/ +void +displayStatistics(const PtpClock* ptpClock) +{ + +/* + INFO("Clock stats: ofm mean: %lu, ofm median: %d," + "ofm std dev: %lu, observed drift std dev: %d\n", + ptpClock->stats.ofmMean, ptpClock->stats.ofmMedian, + ptpClock->stats.ofmStdDev, ptpClock->stats.driftStdDev); +*/ + +} + +/**\brief Display All data sets and counters of a PtpClock*/ +void +displayPtpClock(const PtpClock * ptpClock) +{ + + displayDefault(ptpClock); + displayCurrent(ptpClock); + displayParent(ptpClock); + displayGlobal(ptpClock); + displayPort(ptpClock); + displayForeignMaster(ptpClock); + displayBuffer(ptpClock); + displayOthers(ptpClock); + displayCounters(ptpClock); + displayStatistics(ptpClock); + +} diff --git a/src/ptpd/src/leap-seconds.list b/src/ptpd/src/leap-seconds.list new file mode 100644 index 0000000..22fa785 --- /dev/null +++ b/src/ptpd/src/leap-seconds.list @@ -0,0 +1,250 @@ +# +# In the following text, the symbol '#' introduces +# a comment, which continues from that symbol until +# the end of the line. A plain comment line has a +# whitespace character following the comment indicator. +# There are also special comment lines defined below. +# A special comment will always have a non-whitespace +# character in column 2. +# +# A blank line should be ignored. +# +# The following table shows the corrections that must +# be applied to compute International Atomic Time (TAI) +# from the Coordinated Universal Time (UTC) values that +# are transmitted by almost all time services. +# +# The first column shows an epoch as a number of seconds +# since 1 January 1900, 00:00:00 (1900.0 is also used to +# indicate the same epoch.) Both of these time stamp formats +# ignore the complexities of the time scales that were +# used before the current definition of UTC at the start +# of 1972. (See note 3 below.) +# The second column shows the number of seconds that +# must be added to UTC to compute TAI for any timestamp +# at or after that epoch. The value on each line is +# valid from the indicated initial instant until the +# epoch given on the next one or indefinitely into the +# future if there is no next line. +# (The comment on each line shows the representation of +# the corresponding initial epoch in the usual +# day-month-year format. The epoch always begins at +# 00:00:00 UTC on the indicated day. See Note 5 below.) +# +# Important notes: +# +# 1. Coordinated Universal Time (UTC) is often referred to +# as Greenwich Mean Time (GMT). The GMT time scale is no +# longer used, and the use of GMT to designate UTC is +# discouraged. +# +# 2. The UTC time scale is realized by many national +# laboratories and timing centers. Each laboratory +# identifies its realization with its name: Thus +# UTC(NIST), UTC(USNO), etc. The differences among +# these different realizations are typically on the +# order of a few nanoseconds (i.e., 0.000 000 00x s) +# and can be ignored for many purposes. These differences +# are tabulated in Circular T, which is published monthly +# by the International Bureau of Weights and Measures +# (BIPM). See www.bipm.org for more information. +# +# 3. The current definition of the relationship between UTC +# and TAI dates from 1 January 1972. A number of different +# time scales were in use before that epoch, and it can be +# quite difficult to compute precise timestamps and time +# intervals in those "prehistoric" days. For more information, +# consult: +# +# The Explanatory Supplement to the Astronomical +# Ephemeris. +# or +# Terry Quinn, "The BIPM and the Accurate Measurement +# of Time," Proc. of the IEEE, Vol. 79, pp. 894-905, +# July, 1991. +# +# 4. The decision to insert a leap second into UTC is currently +# the responsibility of the International Earth Rotation and +# Reference Systems Service. (The name was changed from the +# International Earth Rotation Service, but the acronym IERS +# is still used.) +# +# Leap seconds are announced by the IERS in its Bulletin C. +# +# See www.iers.org for more details. +# +# Every national laboratory and timing center uses the +# data from the BIPM and the IERS to construct UTC(lab), +# their local realization of UTC. +# +# Although the definition also includes the possibility +# of dropping seconds ("negative" leap seconds), this has +# never been done and is unlikely to be necessary in the +# foreseeable future. +# +# 5. If your system keeps time as the number of seconds since +# some epoch (e.g., NTP timestamps), then the algorithm for +# assigning a UTC time stamp to an event that happens during a positive +# leap second is not well defined. The official name of that leap +# second is 23:59:60, but there is no way of representing that time +# in these systems. +# Many systems of this type effectively stop the system clock for +# one second during the leap second and use a time that is equivalent +# to 23:59:59 UTC twice. For these systems, the corresponding TAI +# timestamp would be obtained by advancing to the next entry in the +# following table when the time equivalent to 23:59:59 UTC +# is used for the second time. Thus the leap second which +# occurred on 30 June 1972 at 23:59:59 UTC would have TAI +# timestamps computed as follows: +# +# ... +# 30 June 1972 23:59:59 (2287785599, first time): TAI= UTC + 10 seconds +# 30 June 1972 23:59:60 (2287785599,second time): TAI= UTC + 11 seconds +# 1 July 1972 00:00:00 (2287785600) TAI= UTC + 11 seconds +# ... +# +# If your system realizes the leap second by repeating 00:00:00 UTC twice +# (this is possible but not usual), then the advance to the next entry +# in the table must occur the second time that a time equivalent to +# 00:00:00 UTC is used. Thus, using the same example as above: +# +# ... +# 30 June 1972 23:59:59 (2287785599): TAI= UTC + 10 seconds +# 30 June 1972 23:59:60 (2287785600, first time): TAI= UTC + 10 seconds +# 1 July 1972 00:00:00 (2287785600,second time): TAI= UTC + 11 seconds +# ... +# +# in both cases the use of timestamps based on TAI produces a smooth +# time scale with no discontinuity in the time interval. However, +# although the long-term behavior of the time scale is correct in both +# methods, the second method is technically not correct because it adds +# the extra second to the wrong day. +# +# This complexity would not be needed for negative leap seconds (if they +# are ever used). The UTC time would skip 23:59:59 and advance from +# 23:59:58 to 00:00:00 in that case. The TAI offset would decrease by +# 1 second at the same instant. This is a much easier situation to deal +# with, since the difficulty of unambiguously representing the epoch +# during the leap second does not arise. +# +# Some systems implement leap seconds by amortizing the leap second +# over the last few minutes of the day. The frequency of the local +# clock is decreased (or increased) to realize the positive (or +# negative) leap second. This method removes the time step described +# above. Although the long-term behavior of the time scale is correct +# in this case, this method introduces an error during the adjustment +# period both in time and in frequency with respect to the official +# definition of UTC. +# +# Questions or comments to: +# Judah Levine +# Time and Frequency Division +# NIST +# Boulder, Colorado +# Judah.Levine@nist.gov +# +# Last Update of leap second values: 8 July 2016 +# +# The following line shows this last update date in NTP timestamp +# format. This is the date on which the most recent change to +# the leap second data was added to the file. This line can +# be identified by the unique pair of characters in the first two +# columns as shown below. +# +#$ 3676924800 +# +# The NTP timestamps are in units of seconds since the NTP epoch, +# which is 1 January 1900, 00:00:00. The Modified Julian Day number +# corresponding to the NTP time stamp, X, can be computed as +# +# X/86400 + 15020 +# +# where the first term converts seconds to days and the second +# term adds the MJD corresponding to the time origin defined above. +# The integer portion of the result is the integer MJD for that +# day, and any remainder is the time of day, expressed as the +# fraction of the day since 0 hours UTC. The conversion from day +# fraction to seconds or to hours, minutes, and seconds may involve +# rounding or truncation, depending on the method used in the +# computation. +# +# The data in this file will be updated periodically as new leap +# seconds are announced. In addition to being entered on the line +# above, the update time (in NTP format) will be added to the basic +# file name leap-seconds to form the name leap-seconds.. +# In addition, the generic name leap-seconds.list will always point to +# the most recent version of the file. +# +# This update procedure will be performed only when a new leap second +# is announced. +# +# The following entry specifies the expiration date of the data +# in this file in units of seconds since the origin at the instant +# 1 January 1900, 00:00:00. This expiration date will be changed +# at least twice per year whether or not a new leap second is +# announced. These semi-annual changes will be made no later +# than 1 June and 1 December of each year to indicate what +# action (if any) is to be taken on 30 June and 31 December, +# respectively. (These are the customary effective dates for new +# leap seconds.) This expiration date will be identified by a +# unique pair of characters in columns 1 and 2 as shown below. +# In the unlikely event that a leap second is announced with an +# effective date other than 30 June or 31 December, then this +# file will be edited to include that leap second as soon as it is +# announced or at least one month before the effective date +# (whichever is later). +# If an announcement by the IERS specifies that no leap second is +# scheduled, then only the expiration date of the file will +# be advanced to show that the information in the file is still +# current -- the update time stamp, the data and the name of the file +# will not change. +# +# Updated through IERS Bulletin C52 +# File expires on: 28 June 2017 +# +#@ 3707596800 +# +2272060800 10 # 1 Jan 1972 +2287785600 11 # 1 Jul 1972 +2303683200 12 # 1 Jan 1973 +2335219200 13 # 1 Jan 1974 +2366755200 14 # 1 Jan 1975 +2398291200 15 # 1 Jan 1976 +2429913600 16 # 1 Jan 1977 +2461449600 17 # 1 Jan 1978 +2492985600 18 # 1 Jan 1979 +2524521600 19 # 1 Jan 1980 +2571782400 20 # 1 Jul 1981 +2603318400 21 # 1 Jul 1982 +2634854400 22 # 1 Jul 1983 +2698012800 23 # 1 Jul 1985 +2776982400 24 # 1 Jan 1988 +2840140800 25 # 1 Jan 1990 +2871676800 26 # 1 Jan 1991 +2918937600 27 # 1 Jul 1992 +2950473600 28 # 1 Jul 1993 +2982009600 29 # 1 Jul 1994 +3029443200 30 # 1 Jan 1996 +3076704000 31 # 1 Jul 1997 +3124137600 32 # 1 Jan 1999 +3345062400 33 # 1 Jan 2006 +3439756800 34 # 1 Jan 2009 +3550089600 35 # 1 Jul 2012 +3644697600 36 # 1 Jul 2015 +3692217600 37 # 1 Jan 2017 +# +# the following special comment contains the +# hash value of the data in this file computed +# use the secure hash algorithm as specified +# by FIPS 180-1. See the files in ~/pub/sha for +# the details of how this hash value is +# computed. Note that the hash computation +# ignores comments and whitespace characters +# in data lines. It includes the NTP values +# of both the last modification time and the +# expiration time of the file, but not the +# white space on those lines. +# the hash line is also ignored in the +# computation. +# +#h dacf2c42 2c4765d6 3c797af8 2cf630eb 699c8c67 diff --git a/src/ptpd/src/management.c b/src/ptpd/src/management.c new file mode 100644 index 0000000..66cdda9 --- /dev/null +++ b/src/ptpd/src/management.c @@ -0,0 +1,1896 @@ +/*- + * Copyright (c) 2012-2015 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file management.c + * @date Wed Jul 27 13:07:30 CDT 2011 + * + * @brief Routines to handle incoming management messages + * + * + */ + +#include "ptpd.h" + +static void handleMMNullManagement(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMClockDescription(MsgManagement*, MsgManagement*, RunTimeOpts*, PtpClock*); +static void handleMMSlaveOnly(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMUserDescription(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMSaveInNonVolatileStorage(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMResetNonVolatileStorage(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMInitialize(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMDefaultDataSet(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMCurrentDataSet(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMParentDataSet(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMTimePropertiesDataSet(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMPortDataSet(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMPriority1(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMPriority2(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMDomain(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMLogAnnounceInterval(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMAnnounceReceiptTimeout(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMLogSyncInterval(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMVersionNumber(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMEnablePort(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMDisablePort(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMTime(MsgManagement*, MsgManagement*, PtpClock*, const RunTimeOpts*); +static void handleMMClockAccuracy(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMUtcProperties(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMTraceabilityProperties(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMTimescaleProperties(MsgManagement*, MsgManagement*, PtpClock*); + +static void handleMMUnicastNegotiationEnable(MsgManagement*, MsgManagement*, PtpClock*, RunTimeOpts*); +static void handleMMDelayMechanism(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMLogMinPdelayReqInterval(MsgManagement*, MsgManagement*, PtpClock*); +static void handleMMErrorStatus(MsgManagement*); +static void handleErrorManagementMessage(MsgManagement *incoming, MsgManagement *outgoing, + PtpClock *ptpClock, Enumeration16 mgmtId, + Enumeration16 errorId); +#if 0 +static void issueManagement(MsgHeader*,MsgManagement*,const RunTimeOpts*,PtpClock*); +#endif +static void issueManagementRespOrAck(MsgManagement*, Integer32, const RunTimeOpts*,PtpClock*); +static void issueManagementErrorStatus(MsgManagement*, Integer32, const RunTimeOpts*,PtpClock*); + +void +handleManagement(MsgHeader *header, + Boolean isFromSelf, Integer32 sourceAddress, RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + DBGV("Management message received : \n"); + Integer32 dst; + + int tlvOffset = 0; + int tlvFound = 0; + + MsgManagement *mgmtMsg = &ptpClock->msgTmp.manage; + + /* + * If request was unicast, reply to source, always, even if we ourselves are running multicast. + * This is not in line with the standard, but allows us to always reply to unicast management messages. + * This is a good thing - this allows us to always be able to monitor the node using unicast management messages. + */ + + if ( (header->flagField0 & PTP_UNICAST) == PTP_UNICAST) { + dst = sourceAddress; + } else { + dst = 0; + } + + if (isFromSelf) { + DBGV("handleManagement: Ignore message from self \n"); + return; + } + + if(!rtOpts->managementEnabled) { + DBGV("Dropping management message - management message support disabled"); + ptpClock->counters.discardedMessages++; + return; + } + + /* loop over all supported TLVs as if they came in separate messages */ + while(msgUnpackManagement(ptpClock->msgIbuf,mgmtMsg, header, ptpClock, tlvOffset)) { + + if(mgmtMsg->tlv == NULL) { + + if(tlvFound==0) { + DBGV("handleManagement: No TLVs in message\n"); + ptpClock->counters.messageFormatErrors++; + } else { + DBGV("handleManagement: No more TLVs\n"); + } + return; + } + + tlvFound++; + + /* accept the message if directed either to us or to all-ones */ + if(!acceptPortIdentity(ptpClock->portDS.portIdentity, mgmtMsg->targetPortIdentity)) + { + DBGV("handleManagement: The management message was not accepted"); + ptpClock->counters.discardedMessages++; + goto end; + } + + if(!rtOpts->managementSetEnable && + (mgmtMsg->actionField == SET || + mgmtMsg->actionField == COMMAND)) { + DBGV("Dropping SET/COMMAND management message - read-only mode enabled"); + ptpClock->counters.discardedMessages++; + goto end; + } + + /* is this an error status management TLV? */ + if(mgmtMsg->tlv->tlvType == TLV_MANAGEMENT_ERROR_STATUS) { + DBGV("handleManagement: Error Status TLV\n"); + unpackMMErrorStatus(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock); + handleMMErrorStatus(mgmtMsg); + ptpClock->counters.managementMessagesReceived++; + goto end; + } else if (mgmtMsg->tlv->tlvType != TLV_MANAGEMENT) { + /* do nothing, implemention specific handling */ + DBGV("handleManagement: Currently unsupported management TLV type\n"); + ptpClock->counters.discardedMessages++; + goto end; + } + + /* if this is a SET, there is potential for applying new config */ + if (mgmtMsg->actionField & (SET | COMMAND)) { + ptpClock->managementConfig = dictionary_new(0); + dictionary_merge(rtOpts->currentConfig, ptpClock->managementConfig, 1, 0, NULL); + } + + switch(mgmtMsg->tlv->managementId) + { + case MM_NULL_MANAGEMENT: + DBGV("handleManagement: Null Management\n"); + handleMMNullManagement(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_CLOCK_DESCRIPTION: + DBGV("handleManagement: Clock Description\n"); + if(mgmtMsg->actionField != GET && !unpackMMClockDescription(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMClockDescription(mgmtMsg, &ptpClock->outgoingManageTmp, rtOpts, ptpClock); + break; + case MM_USER_DESCRIPTION: + DBGV("handleManagement: User Description\n"); + if(mgmtMsg->actionField != GET && !unpackMMUserDescription(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMUserDescription(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_SAVE_IN_NON_VOLATILE_STORAGE: + DBGV("handleManagement: Save In Non-Volatile Storage\n"); + handleMMSaveInNonVolatileStorage(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_RESET_NON_VOLATILE_STORAGE: + DBGV("handleManagement: Reset Non-Volatile Storage\n"); + handleMMResetNonVolatileStorage(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_INITIALIZE: + DBGV("handleManagement: Initialize\n"); + if(mgmtMsg->actionField != GET && !unpackMMInitialize(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMInitialize(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_DEFAULT_DATA_SET: + DBGV("handleManagement: Default Data Set\n"); + if(mgmtMsg->actionField != GET && !unpackMMDefaultDataSet(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMDefaultDataSet(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_CURRENT_DATA_SET: + DBGV("handleManagement: Current Data Set\n"); + if(mgmtMsg->actionField != GET && !unpackMMCurrentDataSet(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMCurrentDataSet(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_PARENT_DATA_SET: + DBGV("handleManagement: Parent Data Set\n"); + if(mgmtMsg->actionField != GET && !unpackMMParentDataSet(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMParentDataSet(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_TIME_PROPERTIES_DATA_SET: + DBGV("handleManagement: TimeProperties Data Set\n"); + if(mgmtMsg->actionField != GET && !unpackMMTimePropertiesDataSet(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMTimePropertiesDataSet(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_PORT_DATA_SET: + DBGV("handleManagement: Port Data Set\n"); + if(mgmtMsg->actionField != GET && !unpackMMPortDataSet(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMPortDataSet(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_PRIORITY1: + DBGV("handleManagement: Priority1\n"); + if(mgmtMsg->actionField != GET && !unpackMMPriority1(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMPriority1(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_PRIORITY2: + DBGV("handleManagement: Priority2\n"); + if(mgmtMsg->actionField != GET && !unpackMMPriority2(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMPriority2(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_DOMAIN: + DBGV("handleManagement: Domain\n"); + if(mgmtMsg->actionField != GET && !unpackMMDomain(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMDomain(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_SLAVE_ONLY: + DBGV("handleManagement: Slave Only\n"); + if(mgmtMsg->actionField != GET && !unpackMMSlaveOnly(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMSlaveOnly(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_LOG_ANNOUNCE_INTERVAL: + DBGV("handleManagement: Log Announce Interval\n"); + if(mgmtMsg->actionField != GET && !unpackMMLogAnnounceInterval(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMLogAnnounceInterval(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_ANNOUNCE_RECEIPT_TIMEOUT: + DBGV("handleManagement: Announce Receipt Timeout\n"); + if(mgmtMsg->actionField != GET && !unpackMMAnnounceReceiptTimeout(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMAnnounceReceiptTimeout(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_LOG_SYNC_INTERVAL: + DBGV("handleManagement: Log Sync Interval\n"); + if(mgmtMsg->actionField != GET && !unpackMMLogSyncInterval(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMLogSyncInterval(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_VERSION_NUMBER: + DBGV("handleManagement: Version Number\n"); + if(mgmtMsg->actionField != GET && !unpackMMVersionNumber(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMVersionNumber(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_ENABLE_PORT: + DBGV("handleManagement: Enable Port\n"); + handleMMEnablePort(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_DISABLE_PORT: + DBGV("handleManagement: Disable Port\n"); + handleMMDisablePort(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_TIME: + DBGV("handleManagement: Time\n"); + if(mgmtMsg->actionField != GET && !unpackMMTime(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMTime(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock, rtOpts); + break; + case MM_CLOCK_ACCURACY: + DBGV("handleManagement: Clock Accuracy\n"); + if(mgmtMsg->actionField != GET && !unpackMMClockAccuracy(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMClockAccuracy(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_UTC_PROPERTIES: + DBGV("handleManagement: Utc Properties\n"); + if(mgmtMsg->actionField != GET && !unpackMMUtcProperties(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMUtcProperties(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_TRACEABILITY_PROPERTIES: + DBGV("handleManagement: Traceability Properties\n"); + if(mgmtMsg->actionField != GET && !unpackMMTraceabilityProperties(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMTraceabilityProperties(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_TIMESCALE_PROPERTIES: + DBGV("handleManagement: Timescale Properties\n"); + if(mgmtMsg->actionField != GET && !unpackMMTimescaleProperties(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMTimescaleProperties(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_UNICAST_NEGOTIATION_ENABLE: + DBGV("handleManagement: Unicast Negotiation Enable\n"); + if(mgmtMsg->actionField != GET && !unpackMMUnicastNegotiationEnable(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMUnicastNegotiationEnable(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock, rtOpts); + break; + case MM_DELAY_MECHANISM: + DBGV("handleManagement: Delay Mechanism\n"); + if(mgmtMsg->actionField != GET && !unpackMMDelayMechanism(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMDelayMechanism(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_LOG_MIN_PDELAY_REQ_INTERVAL: + DBGV("handleManagement: Log Min Pdelay Req Interval\n"); + if(mgmtMsg->actionField != GET && !unpackMMLogMinPdelayReqInterval(ptpClock->msgIbuf, tlvOffset, mgmtMsg, ptpClock)) { + DBG("handleManagement: (bufGuard) error while unpacking management %02x\n", mgmtMsg->tlv->managementId); + ptpClock->counters.messageFormatErrors++; + goto end; + } + handleMMLogMinPdelayReqInterval(mgmtMsg, &ptpClock->outgoingManageTmp, ptpClock); + break; + case MM_FAULT_LOG: + case MM_FAULT_LOG_RESET: + case MM_PATH_TRACE_LIST: + case MM_PATH_TRACE_ENABLE: + case MM_GRANDMASTER_CLUSTER_TABLE: + case MM_UNICAST_MASTER_TABLE: + case MM_UNICAST_MASTER_MAX_TABLE_SIZE: + case MM_ACCEPTABLE_MASTER_TABLE: + case MM_ACCEPTABLE_MASTER_TABLE_ENABLED: + case MM_ACCEPTABLE_MASTER_MAX_TABLE_SIZE: + case MM_ALTERNATE_MASTER: + case MM_ALTERNATE_TIME_OFFSET_ENABLE: + case MM_ALTERNATE_TIME_OFFSET_NAME: + case MM_ALTERNATE_TIME_OFFSET_MAX_KEY: + case MM_ALTERNATE_TIME_OFFSET_PROPERTIES: + case MM_TRANSPARENT_CLOCK_DEFAULT_DATA_SET: + case MM_TRANSPARENT_CLOCK_PORT_DATA_SET: + case MM_PRIMARY_DOMAIN: + DBGV("handleManagement: Currently unsupported managementTLV %d\n", + mgmtMsg->tlv->managementId); + handleErrorManagementMessage(mgmtMsg, &ptpClock->outgoingManageTmp, + ptpClock, mgmtMsg->tlv->managementId, + NOT_SUPPORTED); + ptpClock->counters.discardedMessages++; + break; + default: + DBGV("handleManagement: Unknown managementTLV %d\n", + mgmtMsg->tlv->managementId); + handleErrorManagementMessage(mgmtMsg, &ptpClock->outgoingManageTmp, + ptpClock, mgmtMsg->tlv->managementId, + NO_SUCH_ID); + ptpClock->counters.discardedMessages++; + } + /* send management message response or acknowledge */ + if(ptpClock->outgoingManageTmp.tlv->tlvType == TLV_MANAGEMENT) { + if(ptpClock->outgoingManageTmp.actionField == RESPONSE || + ptpClock->outgoingManageTmp.actionField == ACKNOWLEDGE) { + issueManagementRespOrAck(&ptpClock->outgoingManageTmp, dst, rtOpts, ptpClock); + } + } else if(ptpClock->outgoingManageTmp.tlv->tlvType == TLV_MANAGEMENT_ERROR_STATUS) { + issueManagementErrorStatus(&ptpClock->outgoingManageTmp, dst, rtOpts, ptpClock); + } + + end: + /* Movin' on up! */ + tlvOffset += TL_LENGTH + mgmtMsg->tlv->lengthField; + /* cleanup msgTmp managementTLV */ + freeManagementTLV(mgmtMsg); + /* cleanup outgoing managementTLV */ + freeManagementTLV(&ptpClock->outgoingManageTmp); + + } + + if(ptpClock->managementConfig != NULL) { + NOTICE("SET / COMMAND management message received - looking for configuration changes\n"); + applyConfig(ptpClock->managementConfig, rtOpts, ptpClock); + dictionary_del(&ptpClock->managementConfig); + } + + if(!tlvFound) { + DBGV("handleManagement: No TLVs in message\n"); + ptpClock->counters.messageFormatErrors++; + } else { + ptpClock->counters.managementMessagesReceived++; + DBGV("handleManagement: No more TLVs\n"); + } + + +} + + +/**\brief Initialize outgoing management message fields*/ +void initOutgoingMsgManagement(MsgManagement* incoming, MsgManagement* outgoing, PtpClock *ptpClock) +{ + /* set header fields */ + outgoing->header.transportSpecific = ptpClock->portDS.transportSpecific; + outgoing->header.messageType = MANAGEMENT; + outgoing->header.versionPTP = ptpClock->portDS.versionNumber; + outgoing->header.domainNumber = ptpClock->defaultDS.domainNumber; + /* set header flagField to zero for management messages, Spec 13.3.2.6 */ + outgoing->header.flagField0 = 0x00; + outgoing->header.flagField1 = 0x00; + outgoing->header.correctionField.msb = 0; + outgoing->header.correctionField.lsb = 0; + copyPortIdentity(&outgoing->header.sourcePortIdentity, &ptpClock->portDS.portIdentity); + outgoing->header.sequenceId = incoming->header.sequenceId; + outgoing->header.controlField = 0x4; /* deprecrated for ptp version 2 */ + outgoing->header.logMessageInterval = 0x7F; + + /* set management message fields */ + copyPortIdentity( &outgoing->targetPortIdentity, &incoming->header.sourcePortIdentity ); + outgoing->startingBoundaryHops = incoming->startingBoundaryHops - incoming->boundaryHops; + outgoing->boundaryHops = outgoing->startingBoundaryHops; + outgoing->actionField = 0; /* set default action, avoid uninitialized value */ + + /* init managementTLV */ + XMALLOC(outgoing->tlv, sizeof(ManagementTLV)); + outgoing->tlv->dataField = NULL; + outgoing->tlv->lengthField = 0; +} + +/**\brief Handle incoming NULL_MANAGEMENT message*/ +void handleMMNullManagement(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received NULL_MANAGEMENT message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_NULL_MANAGEMENT; + + switch(incoming->actionField) + { + case GET: + outgoing->actionField = RESPONSE; + DBGV(" GET mgmt msg\n"); + break; + case SET: + outgoing->actionField = RESPONSE; + DBGV(" SET mgmt msg\n"); + break; + case COMMAND: + outgoing->actionField = ACKNOWLEDGE; + DBGV(" COMMAND mgmt msg\n"); + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_NULL_MANAGEMENT, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming CLOCK_DESCRIPTION management message*/ +void handleMMClockDescription(MsgManagement* incoming, MsgManagement* outgoing, RunTimeOpts *rtOpts, PtpClock* ptpClock) +{ + DBGV("received CLOCK_DESCRIPTION management message \n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_CLOCK_DESCRIPTION; + + MMClockDescription *data = NULL; + switch(incoming->actionField) + { + case GET: + DBGV(" GET action \n"); + /* Table 38 */ + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof( MMClockDescription)); + data = (MMClockDescription*)outgoing->tlv->dataField; + memset(data, 0, sizeof( MMClockDescription)); + /* GET actions */ + /* this is an ordnary node, clockType field bit 0 is one */ + data->clockType0 = 0x80; + data->clockType1 = 0x00; + /* physical layer protocol */ + data->physicalLayerProtocol.lengthField = sizeof(PROTOCOL) - 1; + XMALLOC(data->physicalLayerProtocol.textField, + data->physicalLayerProtocol.lengthField); + memcpy(data->physicalLayerProtocol.textField, + &PROTOCOL, + data->physicalLayerProtocol.lengthField); + /* physical address */ + data->physicalAddress.addressLength = PTP_UUID_LENGTH; + XMALLOC(data->physicalAddress.addressField, PTP_UUID_LENGTH); + memcpy(data->physicalAddress.addressField, + ptpClock->netPath.interfaceID, + PTP_UUID_LENGTH); + /* protocol address */ + data->protocolAddress.addressLength = 4; + data->protocolAddress.networkProtocol = 1; + XMALLOC(data->protocolAddress.addressField, + data->protocolAddress.addressLength); + memcpy(data->protocolAddress.addressField, + &ptpClock->netPath.interfaceAddr.s_addr, + data->protocolAddress.addressLength); + /* manufacturerIdentity OUI */ + data->manufacturerIdentity0 = MANUFACTURER_ID_OUI0; + data->manufacturerIdentity1 = MANUFACTURER_ID_OUI1; + data->manufacturerIdentity2 = MANUFACTURER_ID_OUI2; + /* reserved */ + data->reserved = 0; + /* product description */ + tmpsnprintf(tmpStr, 64, PRODUCT_DESCRIPTION, rtOpts->productDescription); + data->productDescription.lengthField = strlen(tmpStr); + XMALLOC(data->productDescription.textField, + data->productDescription.lengthField); + memcpy(data->productDescription.textField, + tmpStr, + data->productDescription.lengthField); + /* revision data */ + data->revisionData.lengthField = sizeof(REVISION) - 1; + XMALLOC(data->revisionData.textField, + data->revisionData.lengthField); + memcpy(data->revisionData.textField, + &REVISION, + data->revisionData.lengthField); + /* user description */ + data->userDescription.lengthField = strlen(ptpClock->userDescription); + XMALLOC(data->userDescription.textField, + data->userDescription.lengthField); + memcpy(data->userDescription.textField, + ptpClock->userDescription, + data->userDescription.lengthField); + /* profile identity is zero unless specific profile is implemented */ + memcpy(&data->profileIdentity0, &ptpClock->profileIdentity, 6); + break; + case RESPONSE: + DBGV(" RESPONSE action \n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_CLOCK_DESCRIPTION, + NOT_SUPPORTED); + } +} + +/**\brief Handle incoming SLAVE_ONLY management message type*/ +void handleMMSlaveOnly(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received SLAVE_ONLY management message \n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_SLAVE_ONLY; + + MMSlaveOnly* data = NULL; + switch (incoming->actionField) + { + case SET: + DBGV(" SET action \n"); + data = (MMSlaveOnly*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->defaultDS.slaveOnly = data->so; + ptpClock->record_update = TRUE; + setConfig(ptpClock->managementConfig, "ptpengine:slave_only", ptpClock->defaultDS.slaveOnly ? "Y" : "N"); + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action \n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMSlaveOnly)); + data = (MMSlaveOnly*)outgoing->tlv->dataField; + /* GET actions */ + data->so = ptpClock->defaultDS.slaveOnly; + data->reserved = 0x0; + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_SLAVE_ONLY, + NOT_SUPPORTED); + } +} + +/**\brief Handle incoming USER_DESCRIPTION management message type*/ +void handleMMUserDescription(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received USER_DESCRIPTION message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_USER_DESCRIPTION; + + MMUserDescription* data = NULL; + switch(incoming->actionField) + { + case SET: + DBGV(" SET action \n"); + data = (MMUserDescription*)incoming->tlv->dataField; + UInteger8 userDescriptionLength = data->userDescription.lengthField; + if(userDescriptionLength <= USER_DESCRIPTION_MAX) { + memset(ptpClock->userDescription, 0, sizeof(ptpClock->userDescription)); + memcpy(ptpClock->userDescription, data->userDescription.textField, + userDescriptionLength); + /* add null-terminator to make use of C string function strlen later */ + ptpClock->userDescription[userDescriptionLength] = '\0'; + tmpsnprintf(ud, data->userDescription.lengthField+1, "%s", (char*)data->userDescription.textField); + setConfig(ptpClock->managementConfig, "ptpengine:port_description", ud); + } else { + WARNING("management user description exceeds specification length \n"); + } + + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action \n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof( MMUserDescription)); + data = (MMUserDescription*)outgoing->tlv->dataField; + memset(data, 0, sizeof(MMUserDescription)); + /* GET actions */ + data->userDescription.lengthField = strlen(ptpClock->userDescription); + XMALLOC(data->userDescription.textField, + data->userDescription.lengthField); + memcpy(data->userDescription.textField, + ptpClock->userDescription, + data->userDescription.lengthField); + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_USER_DESCRIPTION, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming SAVE_IN_NON_VOLATILE_STORAGE management message type*/ +void handleMMSaveInNonVolatileStorage(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received SAVE_IN_NON_VOLATILE_STORAGE message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_SAVE_IN_NON_VOLATILE_STORAGE; + + switch( incoming->actionField ) + { + case COMMAND: + /* issue a NOT_SUPPORTED error management message, intentionally fall through */ + case ACKNOWLEDGE: + /* issue a NOT_SUPPORTED error management message, intentionally fall through */ + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_SAVE_IN_NON_VOLATILE_STORAGE, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming RESET_NON_VOLATILE_STORAGE management message type*/ +void handleMMResetNonVolatileStorage(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received RESET_NON_VOLATILE_STORAGE message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_RESET_NON_VOLATILE_STORAGE; + + switch( incoming->actionField ) + { + case COMMAND: + /* issue a NOT_SUPPORTED error management message, intentionally fall through */ + case ACKNOWLEDGE: + /* issue a NOT_SUPPORTED error management message, intentionally fall through */ + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_RESET_NON_VOLATILE_STORAGE, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming INITIALIZE management message type*/ +void handleMMInitialize(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received INITIALIZE message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_INITIALIZE; + + MMInitialize* incomingData = NULL; + MMInitialize* outgoingData = NULL; + switch( incoming->actionField ) + { + case COMMAND: + DBGV(" COMMAND action\n"); + outgoing->actionField = ACKNOWLEDGE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMInitialize)); + incomingData = (MMInitialize*)incoming->tlv->dataField; + outgoingData = (MMInitialize*)outgoing->tlv->dataField; + /* Table 45 - INITIALIZATION_KEY enumeration */ + switch( incomingData->initializeKey ) + { + case INITIALIZE_EVENT: + /* cause INITIALIZE event */ + setPortState(ptpClock, PTP_INITIALIZING); + break; + default: + /* do nothing, implementation specific */ + DBGV("initializeKey != 0, do nothing\n"); + } + outgoingData->initializeKey = incomingData->initializeKey; + break; + case ACKNOWLEDGE: + DBGV(" ACKNOWLEDGE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_INITIALIZE, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming DEFAULT_DATA_SET management message type*/ +void handleMMDefaultDataSet(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received DEFAULT_DATA_SET message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_DEFAULT_DATA_SET; + + MMDefaultDataSet* data = NULL; + switch( incoming->actionField ) + { + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMDefaultDataSet)); + data = (MMDefaultDataSet*)outgoing->tlv->dataField; + /* GET actions */ + /* get bit and align for slave only */ + Octet so = ptpClock->defaultDS.slaveOnly << 1; + /* get bit and align by shifting right 1 since TWO_STEP_FLAG is either 0b00 or 0b10 */ + Octet tsc = ptpClock->defaultDS.twoStepFlag >> 1; + data->so_tsc = so | tsc; + data->reserved0 = 0x0; + data->numberPorts = ptpClock->defaultDS.numberPorts; + data->priority1 = ptpClock->defaultDS.priority1; + data->clockQuality.clockAccuracy = ptpClock->defaultDS.clockQuality.clockAccuracy; + data->clockQuality.clockClass = ptpClock->defaultDS.clockQuality.clockClass; + data->clockQuality.offsetScaledLogVariance = + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance; + data->priority2 = ptpClock->defaultDS.priority2; + /* copy clockIdentity */ + copyClockIdentity(data->clockIdentity, ptpClock->defaultDS.clockIdentity); + data->domainNumber = ptpClock->defaultDS.domainNumber; + data->reserved1 = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_DEFAULT_DATA_SET, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming CURRENT_DATA_SET management message type*/ +void handleMMCurrentDataSet(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received CURRENT_DATA_SET message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_CURRENT_DATA_SET; + + MMCurrentDataSet *data = NULL; + switch( incoming->actionField ) + { + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof( MMCurrentDataSet)); + data = (MMCurrentDataSet*)outgoing->tlv->dataField; + /* GET actions */ + data->stepsRemoved = ptpClock->currentDS.stepsRemoved; + TimeInterval oFM; + oFM.scaledNanoseconds.lsb = 0; + oFM.scaledNanoseconds.msb = 0; + internalTime_to_integer64(ptpClock->currentDS.offsetFromMaster, &oFM.scaledNanoseconds); + data->offsetFromMaster.scaledNanoseconds.lsb = oFM.scaledNanoseconds.lsb; + data->offsetFromMaster.scaledNanoseconds.msb = oFM.scaledNanoseconds.msb; + TimeInterval mPD; + mPD.scaledNanoseconds.lsb = 0; + mPD.scaledNanoseconds.msb = 0; + internalTime_to_integer64(ptpClock->currentDS.meanPathDelay, &mPD.scaledNanoseconds); + data->meanPathDelay.scaledNanoseconds.lsb = mPD.scaledNanoseconds.lsb; + data->meanPathDelay.scaledNanoseconds.msb = mPD.scaledNanoseconds.msb; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_CURRENT_DATA_SET, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming PARENT_DATA_SET management message type*/ +void handleMMParentDataSet(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received PARENT_DATA_SET message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_PARENT_DATA_SET; + + MMParentDataSet *data = NULL; + switch( incoming->actionField ) + { + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMParentDataSet)); + data = (MMParentDataSet*)outgoing->tlv->dataField; + /* GET actions */ + copyPortIdentity(&data->parentPortIdentity, &ptpClock->parentDS.parentPortIdentity); + data->PS = ptpClock->parentDS.parentStats; + data->reserved = 0; + data->observedParentOffsetScaledLogVariance = + ptpClock->parentDS.observedParentOffsetScaledLogVariance; + data->observedParentClockPhaseChangeRate = + ptpClock->parentDS.observedParentClockPhaseChangeRate; + data->grandmasterPriority1 = ptpClock->parentDS.grandmasterPriority1; + data->grandmasterClockQuality.clockAccuracy = + ptpClock->parentDS.grandmasterClockQuality.clockAccuracy; + data->grandmasterClockQuality.clockClass = + ptpClock->parentDS.grandmasterClockQuality.clockClass; + data->grandmasterClockQuality.offsetScaledLogVariance = + ptpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance; + data->grandmasterPriority2 = ptpClock->parentDS.grandmasterPriority2; + copyClockIdentity(data->grandmasterIdentity, ptpClock->parentDS.grandmasterIdentity); + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_PARENT_DATA_SET, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming PROPERTIES_DATA_SET management message type*/ +void handleMMTimePropertiesDataSet(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received TIME_PROPERTIES message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_TIME_PROPERTIES_DATA_SET; + + MMTimePropertiesDataSet *data = NULL; + switch( incoming->actionField ) + { + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMTimePropertiesDataSet)); + data = (MMTimePropertiesDataSet*)outgoing->tlv->dataField; + /* GET actions */ + data->currentUtcOffset = ptpClock->timePropertiesDS.currentUtcOffset; + Octet ftra = SET_FIELD(ptpClock->timePropertiesDS.frequencyTraceable, FTRA); + Octet ttra = SET_FIELD(ptpClock->timePropertiesDS.timeTraceable, TTRA); + Octet ptp = SET_FIELD(ptpClock->timePropertiesDS.ptpTimescale, PTPT); + Octet utcv = SET_FIELD(ptpClock->timePropertiesDS.currentUtcOffsetValid, UTCV); + Octet li59 = SET_FIELD(ptpClock->timePropertiesDS.leap59, LI59); + Octet li61 = SET_FIELD(ptpClock->timePropertiesDS.leap61, LI61); + data->ftra_ttra_ptp_utcv_li59_li61 = ftra | ttra | ptp | utcv | li59 | li61; + data->timeSource = ptpClock->timePropertiesDS.timeSource; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_TIME_PROPERTIES_DATA_SET, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming PORT_DATA_SET management message type*/ +void handleMMPortDataSet(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received PORT_DATA_SET message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_PORT_DATA_SET; + + MMPortDataSet *data = NULL; + switch( incoming->actionField ) + { + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMPortDataSet)); + data = (MMPortDataSet*)outgoing->tlv->dataField; + copyPortIdentity(&data->portIdentity, &ptpClock->portDS.portIdentity); + data->portState = ptpClock->portDS.portState; + data->logMinDelayReqInterval = ptpClock->portDS.logMinDelayReqInterval; + TimeInterval pMPD; + pMPD.scaledNanoseconds.lsb = 0; + pMPD.scaledNanoseconds.msb = 0; + internalTime_to_integer64(ptpClock->portDS.peerMeanPathDelay, &pMPD.scaledNanoseconds); + data->peerMeanPathDelay.scaledNanoseconds.lsb = pMPD.scaledNanoseconds.lsb; + data->peerMeanPathDelay.scaledNanoseconds.msb = pMPD.scaledNanoseconds.msb; + data->logAnnounceInterval = ptpClock->portDS.logAnnounceInterval; + data->announceReceiptTimeout = ptpClock->portDS.announceReceiptTimeout; + data->logSyncInterval = ptpClock->portDS.logSyncInterval; + data->delayMechanism = ptpClock->portDS.delayMechanism; + data->logMinPdelayReqInterval = ptpClock->portDS.logMinPdelayReqInterval; + data->reserved = 0; + data->versionNumber = ptpClock->portDS.versionNumber; + break; + case RESPONSE: + DBGV(" RESPONSE action \n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_PORT_DATA_SET, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming PRIORITY1 management message type*/ +void handleMMPriority1(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received PRIORITY1 message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_PRIORITY1; + + MMPriority1* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMPriority1*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->defaultDS.priority1 = data->priority1; + tmpsnprintf(tmpStr, 4, "%d", data->priority1); + setConfig(ptpClock->managementConfig, "ptpengine:priority1", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMPriority1)); + data = (MMPriority1*)outgoing->tlv->dataField; + /* GET actions */ + data->priority1 = ptpClock->defaultDS.priority1; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_PRIORITY1, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming PRIORITY2 management message type*/ +void handleMMPriority2(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received PRIORITY2 message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_PRIORITY2; + + MMPriority2* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMPriority2*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->defaultDS.priority2 = data->priority2; + tmpsnprintf(tmpStr, 4, "%d", data->priority2); + setConfig(ptpClock->managementConfig, "ptpengine:priority2", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMPriority2)); + data = (MMPriority2*)outgoing->tlv->dataField; + /* GET actions */ + data->priority2 = ptpClock->defaultDS.priority2; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_PRIORITY2, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming DOMAIN management message type*/ +void handleMMDomain(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received DOMAIN message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_DOMAIN; + + MMDomain* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMDomain*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->defaultDS.domainNumber = data->domainNumber; + tmpsnprintf(tmpStr, 4, "%d", data->domainNumber); + setConfig(ptpClock->managementConfig, "ptpengine:domain", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMDomain)); + data = (MMDomain*)outgoing->tlv->dataField; + /* GET actions */ + data->domainNumber = ptpClock->defaultDS.domainNumber; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_DOMAIN, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming LOG_ANNOUNCE_INTERVAL management message type*/ +void handleMMLogAnnounceInterval(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received LOG_ANNOUNCE_INTERVAL message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_LOG_ANNOUNCE_INTERVAL; + + MMLogAnnounceInterval* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMLogAnnounceInterval*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->portDS.logAnnounceInterval = data->logAnnounceInterval; + tmpsnprintf(tmpStr, 4, "%d", data->logAnnounceInterval); + setConfig(ptpClock->managementConfig, "ptpengine:log_announce_interval", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMLogAnnounceInterval)); + data = (MMLogAnnounceInterval*)outgoing->tlv->dataField; + /* GET actions */ + data->logAnnounceInterval = ptpClock->portDS.logAnnounceInterval; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_LOG_ANNOUNCE_INTERVAL, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming ANNOUNCE_RECEIPT_TIMEOUT management message type*/ +void handleMMAnnounceReceiptTimeout(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received ANNOUNCE_RECEIPT_TIMEOUT message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_ANNOUNCE_RECEIPT_TIMEOUT; + + MMAnnounceReceiptTimeout* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMAnnounceReceiptTimeout*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->portDS.announceReceiptTimeout = data->announceReceiptTimeout; + tmpsnprintf(tmpStr, 4, "%d", data->announceReceiptTimeout); + setConfig(ptpClock->managementConfig, "ptpengine:announce_receipt_timeout", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMAnnounceReceiptTimeout)); + data = (MMAnnounceReceiptTimeout*)outgoing->tlv->dataField; + /* GET actions */ + data->announceReceiptTimeout = ptpClock->portDS.announceReceiptTimeout; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_ANNOUNCE_RECEIPT_TIMEOUT, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming LOG_SYNC_INTERVAL management message type*/ +void handleMMLogSyncInterval(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received LOG_SYNC_INTERVAL message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_LOG_SYNC_INTERVAL; + + MMLogSyncInterval* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMLogSyncInterval*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->portDS.logSyncInterval = data->logSyncInterval; + tmpsnprintf(tmpStr, 4, "%d", data->logSyncInterval); + setConfig(ptpClock->managementConfig, "ptpengine:log_sync_interval", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMLogSyncInterval)); + data = (MMLogSyncInterval*)outgoing->tlv->dataField; + /* GET actions */ + data->logSyncInterval = ptpClock->portDS.logSyncInterval; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_LOG_SYNC_INTERVAL, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming VERSION_NUMBER management message type*/ +void handleMMVersionNumber(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received VERSION_NUMBER message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_VERSION_NUMBER; + + MMVersionNumber* data = NULL; + switch( incoming->actionField ) + { + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMVersionNumber)); + data = (MMVersionNumber*)outgoing->tlv->dataField; + /* GET actions */ + data->reserved0 = 0x0; + data->versionNumber = ptpClock->portDS.versionNumber; + data->reserved1 = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + /* only version 2. go away. */ + case SET: + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_VERSION_NUMBER, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming ENABLE_PORT management message type*/ +void handleMMEnablePort(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received ENABLE_PORT message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_ENABLE_PORT; + + switch( incoming->actionField ) + { + case COMMAND: + DBGV(" COMMAND action\n"); + outgoing->actionField = ACKNOWLEDGE; + ptpClock->disabled = FALSE; + setConfig(ptpClock->managementConfig, "ptpengine:disabled", "N"); + break; + case ACKNOWLEDGE: + DBGV(" ACKNOWLEDGE action\n"); + /* TODO: implementation specific */ + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_ENABLE_PORT, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming DISABLE_PORT management message type*/ +void handleMMDisablePort(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received DISABLE_PORT message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_DISABLE_PORT; + + switch( incoming->actionField ) + { + case COMMAND: + DBGV(" COMMAND action\n"); + outgoing->actionField = ACKNOWLEDGE; + /* the state machine needs to know that we are not yet disabled but want to be */ + /* ptpClock->disabled = TRUE; */ + setConfig(ptpClock->managementConfig, "ptpengine:disabled", "Y"); + break; + case ACKNOWLEDGE: + DBGV(" ACKNOWLEDGE action\n"); + /* TODO: implementation specific */ + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_DISABLE_PORT, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming TIME management message type*/ +void handleMMTime(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock, const RunTimeOpts* rtOpts) +{ + DBGV("received TIME message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_TIME; + + MMTime* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMTime*)incoming->tlv->dataField; + /* SET actions */ + /* TODO: add currentTime */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMTime)); + data = (MMTime*)outgoing->tlv->dataField; + /* GET actions */ + TimeInternal internalTime; + getTime(&internalTime); + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + fromInternalTime(&internalTime, &data->currentTime); + timestamp_display(&data->currentTime); + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_TIME, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming CLOCK_ACCURACY management message type*/ +void handleMMClockAccuracy(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received CLOCK_ACCURACY message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_CLOCK_ACCURACY; + + MMClockAccuracy* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMClockAccuracy*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->defaultDS.clockQuality.clockAccuracy = data->clockAccuracy; + const char * acc = accToString(data->clockAccuracy); + if(acc != NULL) { + setConfig(ptpClock->managementConfig, "ptpengine:clock_accuracy", acc); + } + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMClockAccuracy)); + data = (MMClockAccuracy*)outgoing->tlv->dataField; + /* GET actions */ + data->clockAccuracy = ptpClock->defaultDS.clockQuality.clockAccuracy; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_CLOCK_ACCURACY, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming UTC_PROPERTIES management message type*/ +void handleMMUtcProperties(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received UTC_PROPERTIES message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_UTC_PROPERTIES; + + MMUtcProperties* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMUtcProperties*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->timePropertiesDS.currentUtcOffset = data->currentUtcOffset; + /* set bit */ + ptpClock->timePropertiesDS.currentUtcOffsetValid = IS_SET(data->utcv_li59_li61, UTCV); + ptpClock->timePropertiesDS.leap59 = IS_SET(data->utcv_li59_li61, LI59); + ptpClock->timePropertiesDS.leap61 = IS_SET(data->utcv_li59_li61, LI61); + tmpsnprintf(tmpStr, 20, "%d", data->currentUtcOffset); + /* todo: setting leap flags can be handy when we remotely controll PTPd GMs */ + setConfig(ptpClock->managementConfig, "ptpengine:utc_offset", tmpStr); + setConfig(ptpClock->managementConfig, "ptpengine:utc_offset_valid", IS_SET(data->utcv_li59_li61, UTCV) ? "Y" : "N"); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMUtcProperties)); + data = (MMUtcProperties*)outgoing->tlv->dataField; + /* GET actions */ + data->currentUtcOffset = ptpClock->timePropertiesDS.currentUtcOffset; + Octet utcv = SET_FIELD(ptpClock->timePropertiesDS.currentUtcOffsetValid, UTCV); + Octet li59 = SET_FIELD(ptpClock->timePropertiesDS.leap59, LI59); + Octet li61 = SET_FIELD(ptpClock->timePropertiesDS.leap61, LI61); + data->utcv_li59_li61 = utcv | li59 | li61; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_UTC_PROPERTIES, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming TRACEABILITY_PROPERTIES management message type*/ +void handleMMTraceabilityProperties(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received TRACEABILITY_PROPERTIES message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_TRACEABILITY_PROPERTIES; + + MMTraceabilityProperties* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMTraceabilityProperties*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->timePropertiesDS.frequencyTraceable = IS_SET(data->ftra_ttra, FTRA); + ptpClock->timePropertiesDS.timeTraceable = IS_SET(data->ftra_ttra, TTRA); + setConfig(ptpClock->managementConfig, "ptpengine:frequency_traceable", IS_SET(data->ftra_ttra, FTRA) ? "Y" : "N"); + setConfig(ptpClock->managementConfig, "ptpengine:time_traceable", IS_SET(data->ftra_ttra, TTRA) ? "Y" : "N"); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMTraceabilityProperties)); + data = (MMTraceabilityProperties*)outgoing->tlv->dataField; + /* GET actions */ + Octet ftra = SET_FIELD(ptpClock->timePropertiesDS.frequencyTraceable, FTRA); + Octet ttra = SET_FIELD(ptpClock->timePropertiesDS.timeTraceable, TTRA); + data->ftra_ttra = ftra | ttra; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_TRACEABILITY_PROPERTIES, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming TIMESCALE_PROPERTIES management message type*/ +void handleMMTimescaleProperties(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received TIMESCALE_PROPERTIES message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_TRACEABILITY_PROPERTIES; + + MMTimescaleProperties* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMTimescaleProperties*)incoming->tlv->dataField; + /* SET actions */ + setConfig(ptpClock->managementConfig, "ptpengine:ptp_timesource", getTimeSourceName(data->timeSource)); + setConfig(ptpClock->managementConfig, "ptpengine:ptp_timescale", data->ptp ? "PTP" : "ARB"); + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMTimescaleProperties)); + data = (MMTimescaleProperties*)outgoing->tlv->dataField; + /* GET actions */ + data->ptp = ptpClock->timePropertiesDS.ptpTimescale; + data->timeSource = ptpClock->timePropertiesDS.timeSource; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_TRACEABILITY_PROPERTIES, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming UNICAST_NEGOTIATION_ENABLE management message type*/ +void handleMMUnicastNegotiationEnable(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock, RunTimeOpts *rtOpts) +{ + DBGV("received UNICAST_NEGOTIATION_ENABLE message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_UNICAST_NEGOTIATION_ENABLE; + + MMUnicastNegotiationEnable* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMUnicastNegotiationEnable*)incoming->tlv->dataField; + /* SET actions */ + setConfig(ptpClock->managementConfig, "ptpengine:unicast_negotiation", data->en ? "Y" : "N"); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMUnicastNegotiationEnable)); + data = (MMUnicastNegotiationEnable*)outgoing->tlv->dataField; + /* GET actions */ + data->en = rtOpts->unicastNegotiation; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_UNICAST_NEGOTIATION_ENABLE, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming DELAY_MECHANISM management message type*/ +void handleMMDelayMechanism(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received DELAY_MECHANISM message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_DELAY_MECHANISM; + + MMDelayMechanism *data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMDelayMechanism*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->portDS.delayMechanism = data->delayMechanism; + const char * mech = delayMechToString(data->delayMechanism); + if(mech != NULL) { + setConfig(ptpClock->managementConfig, "ptpengine:delay_mechanism", mech); + } + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMDelayMechanism)); + data = (MMDelayMechanism*)outgoing->tlv->dataField; + /* GET actions */ + data->delayMechanism = ptpClock->portDS.delayMechanism; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_DELAY_MECHANISM, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming LOG_MIN_PDELAY_REQ_INTERVAL management message type*/ +void handleMMLogMinPdelayReqInterval(MsgManagement* incoming, MsgManagement* outgoing, PtpClock* ptpClock) +{ + DBGV("received LOG_MIN_PDELAY_REQ_INTERVAL message\n"); + + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + outgoing->tlv->tlvType = TLV_MANAGEMENT; + outgoing->tlv->managementId = MM_LOG_MIN_PDELAY_REQ_INTERVAL; + + MMLogMinPdelayReqInterval* data = NULL; + switch( incoming->actionField ) + { + case SET: + DBGV(" SET action\n"); + data = (MMLogMinPdelayReqInterval*)incoming->tlv->dataField; + /* SET actions */ + ptpClock->portDS.logMinPdelayReqInterval = data->logMinPdelayReqInterval; + tmpsnprintf(tmpStr, 4, "%d", data->logMinPdelayReqInterval); + setConfig(ptpClock->managementConfig, "ptpengine:log_peer_delayreq_interval", tmpStr); + ptpClock->record_update = TRUE; + /* intentionally fall through to GET case */ + case GET: + DBGV(" GET action\n"); + outgoing->actionField = RESPONSE; + XMALLOC(outgoing->tlv->dataField, sizeof(MMLogMinPdelayReqInterval)); + data = (MMLogMinPdelayReqInterval*)outgoing->tlv->dataField; + /* GET actions */ + data->logMinPdelayReqInterval = ptpClock->portDS.logMinPdelayReqInterval; + data->reserved = 0x0; + break; + case RESPONSE: + DBGV(" RESPONSE action\n"); + /* TODO: implementation specific */ + break; + default: + DBGV(" unknown actionType \n"); + free(outgoing->tlv); + handleErrorManagementMessage(incoming, outgoing, + ptpClock, MM_LOG_MIN_PDELAY_REQ_INTERVAL, + NOT_SUPPORTED); + } + +} + +/**\brief Handle incoming ERROR_STATUS management message type*/ +void handleMMErrorStatus(MsgManagement *incoming) +{ + (void)incoming; + DBGV("received MANAGEMENT_ERROR_STATUS message \n"); + /* implementation specific */ +} + +/**\brief Handle issuing ERROR_STATUS management message type*/ +void handleErrorManagementMessage(MsgManagement *incoming, MsgManagement *outgoing, PtpClock *ptpClock, Enumeration16 mgmtId, Enumeration16 errorId) +{ + /* init management header fields */ + initOutgoingMsgManagement(incoming, outgoing, ptpClock); + /* init management error status tlv fields */ + outgoing->tlv->tlvType = TLV_MANAGEMENT_ERROR_STATUS; + /* management error status managementId field is the errorId */ + outgoing->tlv->managementId = errorId; + switch(incoming->actionField) + { + case GET: + case SET: + outgoing->actionField = RESPONSE; + break; + case RESPONSE: + outgoing->actionField = ACKNOWLEDGE; + break; + default: + outgoing->actionField = 0; + } + + XMALLOC(outgoing->tlv->dataField, sizeof( MMErrorStatus)); + MMErrorStatus *data = (MMErrorStatus*)outgoing->tlv->dataField; + /* set managementId */ + data->managementId = mgmtId; + data->reserved = 0x00; + data->displayData.lengthField = 0; + data->displayData.textField = NULL; + +} + +#if 0 +static void +issueManagement(MsgHeader *header,MsgManagement *manage,const RunTimeOpts *rtOpts, + PtpClock *ptpClock) +{ + + ptpClock->counters.managementMessagesSent++; + +} +#endif + +static void +issueManagementRespOrAck(MsgManagement *outgoing, Integer32 dst, const RunTimeOpts *rtOpts, + PtpClock *ptpClock) +{ + + /* pack ManagementTLV */ + msgPackManagementTLV( ptpClock->msgObuf, outgoing, ptpClock); + + /* set header messageLength, the outgoing->tlv->lengthField is now valid */ + outgoing->header.messageLength = MANAGEMENT_LENGTH + + TL_LENGTH + + outgoing->tlv->lengthField; + + msgPackManagement( ptpClock->msgObuf, outgoing, ptpClock); + + + if(!netSendGeneral(ptpClock->msgObuf, outgoing->header.messageLength, + &ptpClock->netPath, rtOpts, dst)) { + DBGV("Management response/acknowledge can't be sent -> FAULTY state \n"); + ptpClock->counters.messageSendErrors++; + toState(PTP_FAULTY, rtOpts, ptpClock); + } else { + DBGV("Management response/acknowledge msg sent \n"); + ptpClock->counters.managementMessagesSent++; + } +} + +static void +issueManagementErrorStatus(MsgManagement *outgoing, Integer32 dst, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + /* pack ManagementErrorStatusTLV */ + msgPackManagementErrorStatusTLV( ptpClock->msgObuf, outgoing, ptpClock); + + /* set header messageLength, the outgoing->tlv->lengthField is now valid */ + outgoing->header.messageLength = MANAGEMENT_LENGTH + + TL_LENGTH + + outgoing->tlv->lengthField; + + msgPackManagement( ptpClock->msgObuf, outgoing, ptpClock); + + if(!netSendGeneral(ptpClock->msgObuf, outgoing->header.messageLength, + &ptpClock->netPath, rtOpts, dst)) { + DBGV("Management error status can't be sent -> FAULTY state \n"); + ptpClock->counters.messageSendErrors++; + toState(PTP_FAULTY, rtOpts, ptpClock); + } else { + DBGV("Management error status msg sent \n"); + ptpClock->counters.managementMessagesSent++; + } + +} diff --git a/src/ptpd/src/protocol.c b/src/ptpd/src/protocol.c new file mode 100644 index 0000000..2d6663e --- /dev/null +++ b/src/ptpd/src/protocol.c @@ -0,0 +1,3741 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ +/*- + * Copyright (c) 2012-2016 Wojciech Owczarek, + * Copyright (c) 2011-2012 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen, + * Inaqui Delgado, + * Rick Ratzel, + * National Instruments. + * Copyright (c) 2009-2010 George V. Neville-Neil, + * Steven Kreuzer, + * Martin Burnicki, + * Jan Breuer, + * Gael Mace, + * Alexandre Van Kempen + * + * Copyright (c) 2005-2008 Kendall Correll, Aidan Williams + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file protocol.c + * @date Wed Jun 23 09:40:39 2010 + * + * @brief The code that handles the IEEE-1588 protocol and state machine + * + * + */ +#include "ptpd.h" + +Boolean doInit(RunTimeOpts*,PtpClock*); +static void doState(RunTimeOpts*,PtpClock*); + +void handle(RunTimeOpts*,PtpClock*); + +static void handleAnnounce(MsgHeader*, ssize_t,Boolean, const RunTimeOpts*,PtpClock*); +static void handleSync(const MsgHeader*, ssize_t,TimeInternal*,Boolean,Integer32, Integer32, const RunTimeOpts*,PtpClock*); +static void handleFollowUp(const MsgHeader*, ssize_t,Boolean,const RunTimeOpts*,PtpClock*); +static void handlePdelayReq(MsgHeader*, ssize_t,const TimeInternal*, Integer32, Boolean,const RunTimeOpts*,PtpClock*); +static void handleDelayReq(const MsgHeader*, ssize_t, const TimeInternal*,Integer32, Boolean,const RunTimeOpts*,PtpClock*); +static void handlePdelayResp(const MsgHeader*, TimeInternal* ,ssize_t,Boolean, Integer32, Integer32, const RunTimeOpts*,PtpClock*); +static void handleDelayResp(const MsgHeader*, ssize_t, const RunTimeOpts*,PtpClock*); +static void handlePdelayRespFollowUp(const MsgHeader*, ssize_t, Boolean, const RunTimeOpts*,PtpClock*); + +/* handleManagement in management.c */ +/* handleSignaling in signaling.c */ + +#ifndef PTPD_SLAVE_ONLY /* does not get compiled when building slave only */ +static void issueAnnounce(const RunTimeOpts*,PtpClock*); +static void issueAnnounceSingle(Integer32, UInteger16*, const RunTimeOpts*,PtpClock*); +static void issueSync(const RunTimeOpts*,PtpClock*); +static TimeInternal issueSyncSingle(Integer32, UInteger16*, const RunTimeOpts*,PtpClock*); +static void issueFollowup(const TimeInternal*,const RunTimeOpts*,PtpClock*, Integer32, const UInteger16); +#endif /* PTPD_SLAVE_ONLY */ +static void issuePdelayReq(const RunTimeOpts*,PtpClock*); +static void issueDelayReq(const RunTimeOpts*,PtpClock*); +static void issuePdelayResp(const TimeInternal*,MsgHeader*,Integer32,const RunTimeOpts*,PtpClock*); +static void issueDelayResp(const TimeInternal*,MsgHeader*,Integer32,const RunTimeOpts*,PtpClock*); +static void issuePdelayRespFollowUp(const TimeInternal*,MsgHeader*, Integer32, const RunTimeOpts*,PtpClock*, const UInteger16); + +static void processMessage(RunTimeOpts* rtOpts, PtpClock* ptpClock, TimeInternal* timeStamp, ssize_t length); + +#ifndef PTPD_SLAVE_ONLY /* does not get compiled when building slave only */ +static void processSyncFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock, Integer32 dst, const UInteger16 sequenceId); +static void indexSync(TimeInternal *timeStamp, UInteger16 sequenceId, Integer32 transportAddress, SyncDestEntry *index); +#endif /* PTPD_SLAVE_ONLY */ + +static void processDelayReqFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock); +static void processPdelayReqFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock); +static void processPdelayRespFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock, Integer32 dst, const UInteger16 sequenceId); + +/* this shouldn't really be in protocol.c, it will be moved later */ +static void timestampCorrection(const RunTimeOpts * rtOpts, PtpClock *ptpClock, TimeInternal *timeStamp); + +static Integer32 lookupSyncIndex(TimeInternal *timeStamp, UInteger16 sequenceId, SyncDestEntry *index); +static Integer32 findSyncDestination(TimeInternal *timeStamp, const RunTimeOpts *rtOpts, PtpClock *ptpClock); + + +#ifndef PTPD_SLAVE_ONLY + +/* store transportAddress in an index table */ +static void +indexSync(TimeInternal *timeStamp, UInteger16 sequenceId, Integer32 transportAddress, SyncDestEntry *index) +{ + + uint32_t hash = 0; + +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + struct in_addr tmpAddr; + tmpAddr.s_addr = transportAddress; +#endif /* RUNTIME_DEBUG */ + + if(timeStamp == NULL || index == NULL) { + return; + } + + hash = fnvHash(timeStamp, sizeof(TimeInternal), UNICAST_MAX_DESTINATIONS); + + if(index[hash].transportAddress) { + DBG("indexSync: hash collision - clearing entry %s:%04x\n", inet_ntoa(tmpAddr), hash); + index[hash].transportAddress = 0; + } else { + DBG("indexSync: indexed successfully %s:%04x\n", inet_ntoa(tmpAddr), hash); + index[hash].transportAddress = transportAddress; + } + +} + +#endif /* PTPD_SLAVE_ONLY */ + +/* sync destination index lookup */ +static Integer32 +lookupSyncIndex(TimeInternal *timeStamp, UInteger16 sequenceId, SyncDestEntry *index) +{ + + uint32_t hash = 0; + Integer32 previousAddress; + + if(timeStamp == NULL || index == NULL) { + return 0; + } + + hash = fnvHash(timeStamp, sizeof(TimeInternal), UNICAST_MAX_DESTINATIONS); + + if(index[hash].transportAddress == 0) { + DBG("lookupSyncIndex: cache miss\n"); + return 0; + } else { + DBG("lookupSyncIndex: cache hit - clearing old entry\n"); + previousAddress = index[hash].transportAddress; + index[hash].transportAddress = 0; + return previousAddress; + } + +} + +/* iterative search for Sync destination for the given cached timestamp */ +static Integer32 +findSyncDestination(TimeInternal *timeStamp, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + int i = 0; + + for(i = 0; i < UNICAST_MAX_DESTINATIONS; i++) { + + if(rtOpts->unicastNegotiation) { + if( (timeStamp->seconds == ptpClock->unicastGrants[i].lastSyncTimestamp.seconds) && + (timeStamp->nanoseconds == ptpClock->unicastGrants[i].lastSyncTimestamp.nanoseconds)) { + clearTime(&ptpClock->unicastGrants[i].lastSyncTimestamp); + return ptpClock->unicastGrants[i].transportAddress; + } + } else { + if( (timeStamp->seconds == ptpClock->unicastDestinations[i].lastSyncTimestamp.seconds) && + (timeStamp->nanoseconds == ptpClock->unicastDestinations[i].lastSyncTimestamp.nanoseconds)) { + clearTime(&ptpClock->unicastDestinations[i].lastSyncTimestamp); + return ptpClock->unicastDestinations[i].transportAddress; + } + } + } + + return 0; + +} + +void addForeign(Octet*,MsgHeader*,PtpClock*, UInteger8, UInteger32); + +/* loop forever. doState() has a switch for the actions and events to be + checked for 'port_state'. the actions and events may or may not change + 'port_state' by calling toState(), but once they are done we loop around + again and perform the actions required for the new 'port_state'. */ +void +protocol(RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + DBG("event POWERUP\n"); + + timerStart(&ptpClock->timers[TIMINGDOMAIN_UPDATE_TIMER],timingDomain.updateInterval); + timerStart(&ptpClock->timers[ALARM_UPDATE_TIMER],ALARM_UPDATE_INTERVAL); + + ptpClock->disabled = rtOpts->portDisabled; + + if(ptpClock->disabled) { + toState(PTP_DISABLED, rtOpts, ptpClock); + WARNING("PTP port starting in DISABLED state. Awaiting config change or management message\n"); + /* initialize networking so we can be remotely enabled */ + netShutdown(&ptpClock->netPath); + if (!netInit(&ptpClock->netPath, rtOpts, ptpClock)) { + ERROR("Failed to initialize network in disabled state, will not be able to re-enable!\n"); + } + /* populate the basics required to receive management messages in DISABLED state */ + initData(rtOpts, ptpClock); + updateDatasets(ptpClock, rtOpts); + } else { + toState(PTP_INITIALIZING, rtOpts, ptpClock); + } + if(rtOpts->statusLog.logEnabled) + writeStatusFile(ptpClock, rtOpts, TRUE); + + /* run the status file update every 1 .. 1.2 seconds */ + timerStart(&ptpClock->timers[STATUSFILE_UPDATE_TIMER],rtOpts->statusFileUpdateInterval * (1.0 + 0.2 * getRand())); + timerStart(&ptpClock->timers[PERIODIC_INFO_TIMER],rtOpts->statsUpdateInterval); + + DBG("Debug Initializing...\n"); + + if(rtOpts->statusLog.logEnabled) + writeStatusFile(ptpClock, rtOpts, TRUE); + + for (;;) + { + /* 20110701: this main loop was rewritten to be more clear */ + if(ptpClock->disabled && ptpClock->portDS.portState != PTP_DISABLED) { + toState(PTP_DISABLED, rtOpts, ptpClock); + } + + if(!ptpClock->disabled && ptpClock->portDS.portState == PTP_DISABLED) { + toState(PTP_INITIALIZING, rtOpts, ptpClock); + } + + if (ptpClock->portDS.portState == PTP_INITIALIZING) { + + /* + * DO NOT shut down once started. We have to "wait intelligently", + * that is keep processing signals. If init failed, wait for n seconds + * until next retry, do not exit. Wait in chunks so SIGALRM can interrupt. + */ + if(ptpClock->initFailure) { + usleep(10000); + ptpClock->initFailureTimeout--; + } + + if(!ptpClock->initFailure || ptpClock->initFailureTimeout <= 0) { + if(!doInit(rtOpts, ptpClock)) { + ERROR("PTPd init failed - will retry in %d seconds\n", DEFAULT_FAILURE_WAITTIME); + writeStatusFile(ptpClock, rtOpts, TRUE); + ptpClock->initFailure = TRUE; + ptpClock->initFailureTimeout = 100 * DEFAULT_FAILURE_WAITTIME; + SET_ALARM(ALRM_NETWORK_FLT, TRUE); + } else { + ptpClock->initFailure = FALSE; + ptpClock->initFailureTimeout = 0; + SET_ALARM(ALRM_NETWORK_FLT, FALSE); + } + + } + + } else { + doState(rtOpts, ptpClock); + } + + if(ptpClock->disabled && ptpClock->portDS.portState != PTP_DISABLED) { + toState(PTP_DISABLED, rtOpts, ptpClock); + } + + if (ptpClock->message_activity) + DBGV("activity\n"); + + /* Configuration has changed */ + if(rtOpts->restartSubsystems > 0) { + restartSubsystems(rtOpts, ptpClock); + } + + if (timerExpired(&ptpClock->timers[TIMINGDOMAIN_UPDATE_TIMER])) { + timingDomain.update(&timingDomain); + } + + if(ptpClock->defaultDS.slaveOnly) { + SET_ALARM(ALRM_PORT_STATE, ptpClock->portDS.portState != PTP_SLAVE); + } + + if(ptpClock->defaultDS.clockQuality.clockClass < 128) { + SET_ALARM(ALRM_PORT_STATE, ptpClock->portDS.portState != PTP_MASTER && ptpClock->portDS.portState != PTP_PASSIVE ); + } + + if (timerExpired(&ptpClock->timers[ALARM_UPDATE_TIMER])) { + if(rtOpts->alarmInitialDelay && (ptpClock->alarmDelay > 0)) { + ptpClock->alarmDelay -= ALARM_UPDATE_INTERVAL; + if(ptpClock->alarmDelay <= 0 && rtOpts->alarmsEnabled) { + INFO("Alarm delay expired - starting alarm processing\n"); + enableAlarms(ptpClock->alarms, ALRM_MAX, TRUE); + } + } + updateAlarms(ptpClock->alarms, ALRM_MAX); + } + + + if (timerExpired(&ptpClock->timers[UNICAST_GRANT_TIMER])) { + if(rtOpts->unicastDestinationsSet) { + refreshUnicastGrants(ptpClock->unicastGrants, + ptpClock->unicastDestinationCount, rtOpts, ptpClock); + } else { + refreshUnicastGrants(ptpClock->unicastGrants, + UNICAST_MAX_DESTINATIONS, rtOpts, ptpClock); + } + if(ptpClock->unicastPeerDestination.transportAddress) { + refreshUnicastGrants(&ptpClock->peerGrants, + 1, rtOpts, ptpClock); + + } + } + + /* Perform the heavy signal processing synchronously */ + checkSignals(rtOpts, ptpClock); + } +} + +/* state change wrapper to allow for event generation / control when changing state */ +void setPortState(PtpClock *ptpClock, Enumeration8 state) +{ + + if(ptpClock == NULL) { + return; + } + if(ptpClock->portDS.portState != state) { + ptpClock->portDS.lastPortState = ptpClock->portDS.portState; + DBG("State change from %s to %s\n", portState_getName(ptpClock->portDS.lastPortState), portState_getName(state)); + } + + /* "expected state" checks */ + + ptpClock->portDS.portState = state; + + if(ptpClock->defaultDS.slaveOnly) { + SET_ALARM(ALRM_PORT_STATE, state != PTP_SLAVE); + } else if(ptpClock->defaultDS.clockQuality.clockClass < 128) { + SET_ALARM(ALRM_PORT_STATE, state != PTP_MASTER && state != PTP_PASSIVE ); + } + + +} + +/* perform actions required when leaving 'port_state' and entering 'state' */ +void +toState(UInteger8 state, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + ptpClock->message_activity = TRUE; + + /* leaving state tasks */ + switch (ptpClock->portDS.portState) + { + case PTP_MASTER: + + timerStop(&ptpClock->timers[SYNC_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[ANNOUNCE_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[DELAY_RECEIPT_TIMER]); + timerStop(&ptpClock->timers[MASTER_NETREFRESH_TIMER]); + + if(rtOpts->unicastNegotiation && rtOpts->ipMode==IPMODE_UNICAST) { + cancelAllGrants(ptpClock->unicastGrants, UNICAST_MAX_DESTINATIONS, + rtOpts, ptpClock); + if(ptpClock->portDS.delayMechanism == P2P) { + cancelAllGrants(&ptpClock->peerGrants, 1, + rtOpts, ptpClock); + } + } + + break; + case PTP_SLAVE: + timerStop(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER]); + timerStop(&ptpClock->timers[SYNC_RECEIPT_TIMER]); +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + timerStop(&ptpClock->timers[FOLLOWUP_RECEIPT_TIMER]); +#endif +// ##### SCORE MODIFICATION END ##### + timerStop(&ptpClock->timers[DELAY_RECEIPT_TIMER]); + + if(rtOpts->unicastNegotiation && rtOpts->ipMode==IPMODE_UNICAST && ptpClock->parentGrants != NULL) { + /* do not cancel, just start re-requesting so we can still send a cancel on exit */ + ptpClock->parentGrants->grantData[ANNOUNCE_INDEXED].timeLeft = 0; + ptpClock->parentGrants->grantData[SYNC_INDEXED].timeLeft = 0; + if(ptpClock->portDS.delayMechanism == E2E) { + ptpClock->parentGrants->grantData[DELAY_RESP_INDEXED].timeLeft = 0; + } + + ptpClock->parentGrants = NULL; + + if(ptpClock->portDS.delayMechanism == P2P) { + cancelUnicastTransmission(&ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED], rtOpts, ptpClock); + cancelAllGrants(&ptpClock->peerGrants, 1, rtOpts, ptpClock); + } + } + + if (ptpClock->portDS.delayMechanism == E2E) + timerStop(&ptpClock->timers[DELAYREQ_INTERVAL_TIMER]); + else if (ptpClock->portDS.delayMechanism == P2P) + timerStop(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER]); +/* If statistics are enabled, drift should have been saved already - otherwise save it*/ +#ifndef PTPD_STATISTICS + /* save observed drift value, don't inform user */ + saveDrift(ptpClock, rtOpts, TRUE); +#endif /* PTPD_STATISTICS */ + +#ifdef PTPD_STATISTICS + resetPtpEngineSlaveStats(&ptpClock->slaveStats); + timerStop(&ptpClock->timers[STATISTICS_UPDATE_TIMER]); +#endif /* PTPD_STATISTICS */ + ptpClock->panicMode = FALSE; + ptpClock->panicOver = FALSE; + timerStop(&ptpClock->timers[PANIC_MODE_TIMER]); + initClock(rtOpts, ptpClock); + + case PTP_PASSIVE: + timerStop(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[DELAY_RECEIPT_TIMER]); + timerStop(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER]); + break; + + case PTP_LISTENING: + timerStop(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER]); + timerStop(&ptpClock->timers[SYNC_RECEIPT_TIMER]); +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + timerStop(&ptpClock->timers[FOLLOWUP_RECEIPT_TIMER]); +#endif +// ##### SCORE MODIFICATION END ##### + timerStop(&ptpClock->timers[DELAY_RECEIPT_TIMER]); + + /* we're leaving LISTENING - reset counter */ + if(state != PTP_LISTENING) { + ptpClock->listenCount = 0; + } + break; + case PTP_INITIALIZING: + if(rtOpts->unicastNegotiation) { + if(!ptpClock->defaultDS.slaveOnly) { + + initUnicastGrantTable(ptpClock->unicastGrants, + ptpClock->portDS.delayMechanism, + UNICAST_MAX_DESTINATIONS, NULL, + rtOpts, ptpClock); + + if(rtOpts->unicastDestinationsSet) { + initUnicastGrantTable(ptpClock->unicastGrants, + ptpClock->portDS.delayMechanism, + ptpClock->unicastDestinationCount, ptpClock->unicastDestinations, + rtOpts, ptpClock); + } + + } else { + initUnicastGrantTable(ptpClock->unicastGrants, + ptpClock->portDS.delayMechanism, + ptpClock->unicastDestinationCount, ptpClock->unicastDestinations, + rtOpts, ptpClock); + } + if(ptpClock->unicastPeerDestination.transportAddress) { + initUnicastGrantTable(&ptpClock->peerGrants, + ptpClock->portDS.delayMechanism, + 1, &ptpClock->unicastPeerDestination, + rtOpts, ptpClock); + } + /* this must be set regardless of delay mechanism, + * so functions can see this is a peer table + */ + ptpClock->peerGrants.isPeer = TRUE; + } + break; + default: + break; + } + + /* entering state tasks */ + + ptpClock->counters.stateTransitions++; + + DBG("state %s\n",portState_getName(state)); + + /* + * No need of PRE_MASTER state because of only ordinary clock + * implementation. + */ + + switch (state) + { + case PTP_INITIALIZING: + setPortState(ptpClock, PTP_INITIALIZING); + break; + + case PTP_FAULTY: + setPortState(ptpClock, PTP_FAULTY); + break; + + case PTP_DISABLED: + /* well, theoretically we're still in the previous state, so we're not in breach of standard */ + if(rtOpts->unicastNegotiation && rtOpts->ipMode==IPMODE_UNICAST) { + cancelAllGrants(ptpClock->unicastGrants, ptpClock->unicastDestinationCount, + rtOpts, ptpClock); + } + ptpClock->bestMaster = NULL; + /* see? NOW we're in disabled state */ + setPortState(ptpClock, PTP_DISABLED); + break; + + case PTP_LISTENING: + + if(rtOpts->unicastNegotiation) { + timerStart(&ptpClock->timers[UNICAST_GRANT_TIMER], UNICAST_GRANT_REFRESH_INTERVAL); + } + + /* in Listening state, make sure we don't send anything. Instead we just expect/wait for announces (started below) */ + timerStop(&ptpClock->timers[SYNC_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[ANNOUNCE_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[SYNC_RECEIPT_TIMER]); +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + timerStop(&ptpClock->timers[FOLLOWUP_RECEIPT_TIMER]); +#endif +// ##### SCORE MODIFICATION END ##### + timerStop(&ptpClock->timers[DELAY_RECEIPT_TIMER]); + timerStop(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER]); + timerStop(&ptpClock->timers[DELAYREQ_INTERVAL_TIMER]); + + /* This is (re) started on clock updates only */ + timerStop(&ptpClock->timers[CLOCK_UPDATE_TIMER]); + + /* if we're ignoring announces (disable_bmca), go straight to master */ + if(ptpClock->defaultDS.clockQuality.clockClass <= 127 && rtOpts->disableBMCA) { + DBG("unicast master only and ignoreAnnounce: going into MASTER state\n"); + ptpClock->number_foreign_records = 0; + ptpClock->foreign_record_i = 0; + m1(rtOpts,ptpClock); + toState(PTP_MASTER, rtOpts, ptpClock); + break; + } + + /* + * Count how many _unique_ timeouts happen to us. + * If we were already in Listen mode, then do not count this as a seperate reset, but stil do a new IGMP refresh + */ + if (ptpClock->portDS.portState != PTP_LISTENING) { + ptpClock->resetCount++; + } else { + ptpClock->listenCount++; + if( ptpClock->listenCount >= rtOpts->maxListen ) { + WARNING("Still in LISTENING after %d restarts - restarting transports\n", + rtOpts->maxListen); + toState(PTP_FAULTY, rtOpts, ptpClock); + ptpClock->listenCount = 0; + break; + } + } + + /* Revert to the original DelayReq interval, and ignore the one for the last master */ + ptpClock->portDS.logMinDelayReqInterval = rtOpts->initial_delayreq; + + /* force a IGMP refresh per reset */ + if (rtOpts->ipMode != IPMODE_UNICAST && rtOpts->do_IGMP_refresh && rtOpts->transport != IEEE_802_3) { + /* if multicast refresh failed, restart network - helps recover after driver reloads and such */ + if(!netRefreshIGMP(&ptpClock->netPath, rtOpts, ptpClock)) { + WARNING("Error while refreshing multicast - restarting transports\n"); + toState(PTP_FAULTY, rtOpts, ptpClock); + break; + } + } + + timerStart(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER], + (ptpClock->portDS.announceReceiptTimeout) * + (pow(2,ptpClock->portDS.logAnnounceInterval))); + + ptpClock->announceTimeouts = 0; + + ptpClock->bestMaster = NULL; + + /* + * Log status update only once - condition must be checked before we write the new state, + * but the new state must be eritten before the log message... + */ + if (ptpClock->portDS.portState != state) { + setPortState(ptpClock, PTP_LISTENING); + displayStatus(ptpClock, "Now in state: "); + } else { + setPortState(ptpClock, PTP_LISTENING); + } + + break; +#ifndef PTPD_SLAVE_ONLY + case PTP_MASTER: + if(rtOpts->unicastNegotiation) { + timerStart(&ptpClock->timers[UNICAST_GRANT_TIMER], 1); + } + // ##### SCORE MODIFICATION BEGIN ##### + // timerStart(&ptpClock->timers[SYNC_INTERVAL_TIMER], + // pow(2,ptpClock->portDS.logSyncInterval)); + // The SyncPeriod is now read from the configuration + timerStart(&ptpClock->timers[SYNC_INTERVAL_TIMER], + ((double)(ptpConfig.timebaseConfig.syncPeriodMs)/SCORE_PTP_DEFAULT_SYNC_PERIOD)); + // ##### SCORE MODIFICATION END ##### + + DBG("SYNC INTERVAL TIMER : %f \n", + pow(2,ptpClock->portDS.logSyncInterval)); + timerStart(&ptpClock->timers[ANNOUNCE_INTERVAL_TIMER], + pow(2,ptpClock->portDS.logAnnounceInterval)); + timerStart(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER], + pow(2,ptpClock->portDS.logMinPdelayReqInterval)); + if(ptpClock->portDS.delayMechanism == P2P) { + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinPdelayReqInterval)))); + } + if( rtOpts->do_IGMP_refresh && + rtOpts->transport == UDP_IPV4 && + rtOpts->ipMode != IPMODE_UNICAST && + rtOpts->masterRefreshInterval > 9 ) + timerStart(&ptpClock->timers[MASTER_NETREFRESH_TIMER], + rtOpts->masterRefreshInterval); + setPortState(ptpClock, PTP_MASTER); + displayStatus(ptpClock, "Now in state: "); + + ptpClock->bestMaster = NULL; + + break; +#endif /* PTPD_SLAVE_ONLY */ + case PTP_PASSIVE: + timerStart(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER], + pow(2,ptpClock->portDS.logMinPdelayReqInterval)); + timerStart(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER], + (ptpClock->portDS.announceReceiptTimeout) * + (pow(2,ptpClock->portDS.logAnnounceInterval))); + if(ptpClock->portDS.delayMechanism == P2P) { + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinPdelayReqInterval)))); + } + setPortState(ptpClock, PTP_PASSIVE); + p1(ptpClock, rtOpts); + displayStatus(ptpClock, "Now in state: "); + break; + + case PTP_UNCALIBRATED: + setPortState(ptpClock, PTP_UNCALIBRATED); + break; + + case PTP_SLAVE: + if(rtOpts->unicastNegotiation) { + timerStart(&ptpClock->timers[UNICAST_GRANT_TIMER], 1); + } + if(rtOpts->clockUpdateTimeout > 0) { + timerStart(&ptpClock->timers[CLOCK_UPDATE_TIMER], rtOpts->clockUpdateTimeout); + } + initClock(rtOpts, ptpClock); + /* + * restore the observed drift value using the selected method, + * reset on failure or when -F 0 (default) is used, don't inform user + */ + restoreDrift(ptpClock, rtOpts, TRUE); + + ptpClock->waitingForFollow = FALSE; + ptpClock->waitingForDelayResp = FALSE; + + if(rtOpts->calibrationDelay) { + ptpClock->isCalibrated = FALSE; + } + + // FIXME: clear these vars inside initclock + clearTime(&ptpClock->delay_req_send_time); + clearTime(&ptpClock->delay_req_receive_time); + clearTime(&ptpClock->pdelay_req_send_time); + clearTime(&ptpClock->pdelay_req_receive_time); + clearTime(&ptpClock->pdelay_resp_send_time); + clearTime(&ptpClock->pdelay_resp_receive_time); + + timerStart(&ptpClock->timers[OPERATOR_MESSAGES_TIMER], + OPERATOR_MESSAGES_INTERVAL); + // ##### SCORE MODIFICATION BEGIN ##### toState() +#if SCORE_EXTENSIONS + if( !rtOpts->disableBMCA || !rtOpts->scoreConfig.autosar ) + { +#endif + // ##### SCORE MODIFICATION END ##### + timerStart(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER], + (ptpClock->portDS.announceReceiptTimeout) * + (pow(2,ptpClock->portDS.logAnnounceInterval))); + // ##### SCORE MODIFICATION BEGIN ##### toState() +#if SCORE_EXTENSIONS + } +#endif + // ##### SCORE MODIFICATION END ##### + + timerStart(&ptpClock->timers[SYNC_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logSyncInterval)) + )); + + if(ptpClock->portDS.delayMechanism == E2E) { + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinDelayReqInterval)))); + } + if(ptpClock->portDS.delayMechanism == P2P) { + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinPdelayReqInterval)))); + } + + /* + * Previously, this state transition would start the + * delayreq timer immediately. However, if this was + * faster than the first received sync, then the servo + * would drop the delayResp Now, we only start the + * timer after we receive the first sync (in + * handle_sync()) + */ + ptpClock->syncWaiting = TRUE; + ptpClock->delayRespWaiting = TRUE; + ptpClock->announceTimeouts = 0; + setPortState(ptpClock, PTP_SLAVE); + displayStatus(ptpClock, "Now in state: "); + ptpClock->followUpGap = 0; + +#ifdef PTPD_STATISTICS + if(rtOpts->oFilterMSConfig.enabled) { + ptpClock->oFilterMS.reset(&ptpClock->oFilterMS); + } + if(rtOpts->oFilterSMConfig.enabled) { + ptpClock->oFilterSM.reset(&ptpClock->oFilterSM); + } + if(rtOpts->filterMSOpts.enabled) { + resetDoubleMovingStatFilter(ptpClock->filterMS); + } + if(rtOpts->filterSMOpts.enabled) { + resetDoubleMovingStatFilter(ptpClock->filterSM); + } + clearPtpEngineSlaveStats(&ptpClock->slaveStats); + ptpClock->servo.driftMean = 0; + ptpClock->servo.driftStdDev = 0; + ptpClock->servo.isStable = FALSE; + ptpClock->servo.stableCount = 0; + ptpClock->servo.updateCount = 0; + ptpClock->servo.statsCalculated = FALSE; + ptpClock->servo.statsUpdated = FALSE; + resetDoublePermanentMedian(&ptpClock->servo.driftMedianContainer); + resetDoublePermanentStdDev(&ptpClock->servo.driftStats); + timerStart(&ptpClock->timers[STATISTICS_UPDATE_TIMER], rtOpts->statsUpdateInterval); +#endif /* PTPD_STATISTICS */ + break; + default: + DBG("to unrecognized state\n"); + break; + } + + if (rtOpts->logStatistics) + logStatistics(ptpClock); +} + + +Boolean +doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + char filterMask[200]; + + DBG("manufacturerOUI: %02hhx:%02hhx:%02hhx \n", + MANUFACTURER_ID_OUI0, + MANUFACTURER_ID_OUI1, + MANUFACTURER_ID_OUI2); + /* initialize networking */ + netShutdown(&ptpClock->netPath); + + if(rtOpts->backupIfaceEnabled && + ptpClock->runningBackupInterface) { + rtOpts->ifaceName = rtOpts->backupIfaceName; + } else { + rtOpts->ifaceName = rtOpts->primaryIfaceName; + } + + if (!netInit(&ptpClock->netPath, rtOpts, ptpClock)) { + ERROR("Failed to initialize network\n"); + toState(PTP_FAULTY, rtOpts, ptpClock); + return FALSE; + } + + strncpy(filterMask,FILTER_MASK,199); + + /* initialize other stuff */ + initData(rtOpts, ptpClock); + initClock(rtOpts, ptpClock); + setupPIservo(&ptpClock->servo, rtOpts); + /* restore observed drift and inform user */ + if(ptpClock->defaultDS.clockQuality.clockClass > 127) + restoreDrift(ptpClock, rtOpts, FALSE); + m1(rtOpts, ptpClock ); +// ##### SCORE MODIFICATION BEGIN ##### + msgPackHeader(ptpClock->msgObuf, ptpClock, NULL); +// ##### SCORE MODIFICATION END ##### + + toState(PTP_LISTENING, rtOpts, ptpClock); + + if(rtOpts->statusLog.logEnabled) + writeStatusFile(ptpClock, rtOpts, TRUE); + + return TRUE; +} + +/* handle actions and events for 'port_state' */ +static void +doState(RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + UInteger8 state; + + ptpClock->message_activity = FALSE; + + /* Process record_update (BMC algorithm) before everything else */ + switch (ptpClock->portDS.portState) + { + case PTP_LISTENING: + case PTP_PASSIVE: + case PTP_SLAVE: + case PTP_MASTER: + /*State decision Event*/ + + /* If we received a valid Announce message + * and can use it (record_update), + * or we received a SET management message that + * changed an attribute in ptpClock, + * then run the BMC algorithm + */ + if(ptpClock->record_update) + { + DBG2("event STATE_DECISION_EVENT\n"); + ptpClock->record_update = FALSE; + state = bmc(ptpClock->foreign, rtOpts, ptpClock); + if(state != ptpClock->portDS.portState) + toState(state, rtOpts, ptpClock); + } + break; + + default: + break; + } + + switch (ptpClock->portDS.portState) + { + case PTP_FAULTY: + /* imaginary troubleshooting */ + DBG("event FAULT_CLEARED\n"); + toState(PTP_INITIALIZING, rtOpts, ptpClock); + return; + + case PTP_LISTENING: + case PTP_UNCALIBRATED: + // passive mode behaves like the SLAVE state, in order to wait for the announce timeout of the current active master + case PTP_PASSIVE: + case PTP_SLAVE: + handle(rtOpts, ptpClock); + + /* + * handle SLAVE timers: + * - No Announce message was received + * - Time to send new delayReq (miss of delayResp is not monitored explicitelly) + */ + // ##### SCORE MODIFICATION BEGIN ##### doState() +#if SCORE_EXTENSIONS + if( !rtOpts->disableBMCA || !rtOpts->scoreConfig.autosar ) +#endif + // ##### SCORE MODIFICATION END ##### + if (timerExpired(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER])) + { + DBG("event ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES\n"); + + if(!ptpClock->defaultDS.slaveOnly && + ptpClock->defaultDS.clockQuality.clockClass != SLAVE_ONLY_CLOCK_CLASS) { + ptpClock->number_foreign_records = 0; + ptpClock->foreign_record_i = 0; + ptpClock->bestMaster = NULL; + m1(rtOpts,ptpClock); + toState(PTP_MASTER, rtOpts, ptpClock); + + } else if(ptpClock->portDS.portState != PTP_LISTENING) { +#ifdef PTPD_STATISTICS + /* stop statistics updates */ + timerStop(&ptpClock->timers[STATISTICS_UPDATE_TIMER]); +#endif /* PTPD_STATISTICS */ + + if(ptpClock->announceTimeouts < rtOpts->announceTimeoutGracePeriod) { + /* + * Don't reset yet - just disqualify current GM. + * If another live master exists, it will be selected, + * otherwise, timer will cycle and we will reset. + * Also don't clear the FMR just yet. + */ + if (!ptpClock->bestMaster->disqualified) { + ptpClock->bestMaster->disqualified = TRUE; + WARNING("GM announce timeout, disqualified current best GM\n"); + ptpClock->counters.announceTimeouts++; + } + + if (rtOpts->ipMode != IPMODE_UNICAST && rtOpts->do_IGMP_refresh && rtOpts->transport != IEEE_802_3) { + /* if multicast refresh failed, restart network - helps recover after driver reloads and such */ + if(!netRefreshIGMP(&ptpClock->netPath, rtOpts, ptpClock)) { + WARNING("Error while refreshing multicast - restarting transports\n"); + toState(PTP_FAULTY, rtOpts, ptpClock); + break; + } + } + + if (rtOpts->announceTimeoutGracePeriod > 0) ptpClock->announceTimeouts++; + + INFO("Waiting for new master, %d of %d attempts\n",ptpClock->announceTimeouts,rtOpts->announceTimeoutGracePeriod); + } else { + WARNING("No active masters present. Resetting port.\n"); + ptpClock->number_foreign_records = 0; + ptpClock->foreign_record_i = 0; + ptpClock->bestMaster = NULL; + /* if flipping between primary and backup interface, a full nework re-init is required */ + if(rtOpts->backupIfaceEnabled) { + ptpClock->runningBackupInterface = !ptpClock->runningBackupInterface; + toState(PTP_INITIALIZING, rtOpts, ptpClock); + NOTICE("Now switching to %s interface\n", ptpClock->runningBackupInterface ? + "backup":"primary"); + } else { + + toState(PTP_LISTENING, rtOpts, ptpClock); + } + + } + } else { + + /* if flipping between primary and backup interface, a full nework re-init is required */ + if(rtOpts->backupIfaceEnabled) { + ptpClock->runningBackupInterface = !ptpClock->runningBackupInterface; + toState(PTP_INITIALIZING, rtOpts, ptpClock); + NOTICE("Now switching to %s interface\n", ptpClock->runningBackupInterface ? + "backup":"primary"); + + } else { + /* + * Force a reset when getting a timeout in state listening, that will lead to an IGMP reset + * previously this was not the case when we were already in LISTENING mode + */ + toState(PTP_LISTENING, rtOpts, ptpClock); + } + } + + } + + /* Reset the slave if clock update timeout configured */ + if ( ptpClock->portDS.portState == PTP_SLAVE && (rtOpts->clockUpdateTimeout > 0) && + timerExpired(&ptpClock->timers[CLOCK_UPDATE_TIMER])) { + if(ptpClock->panicMode || rtOpts->noAdjust) { + timerStart(&ptpClock->timers[CLOCK_UPDATE_TIMER], rtOpts->clockUpdateTimeout); + } else { + WARNING("No offset updates in %d seconds - resetting slave\n", + rtOpts->clockUpdateTimeout); + toState(PTP_LISTENING, rtOpts, ptpClock); + } + } + + if (timerExpired(&ptpClock->timers[OPERATOR_MESSAGES_TIMER])) { + resetWarnings(rtOpts, ptpClock); + } + + if(ptpClock->portDS.portState==PTP_SLAVE && rtOpts->calibrationDelay && !ptpClock->isCalibrated) { + if(timerExpired(&ptpClock->timers[CALIBRATION_DELAY_TIMER])) { + ptpClock->isCalibrated = TRUE; + if(ptpClock->clockControl.granted) { + NOTICE("Offset computation now calibrated, enabled clock control\n"); + } else { + NOTICE("Offset computation now calibrated\n"); + } + } else if(!timerRunning(&ptpClock->timers[CALIBRATION_DELAY_TIMER])) { + timerStart(&ptpClock->timers[CALIBRATION_DELAY_TIMER], rtOpts->calibrationDelay); + } + } + + if (ptpClock->portDS.delayMechanism == E2E) { + if(timerExpired(&ptpClock->timers[DELAYREQ_INTERVAL_TIMER])) { + DBG("event DELAYREQ_INTERVAL_TIMEOUT_EXPIRES\n"); + /* if unicast negotiation is enabled, only request if granted */ + if(!rtOpts->unicastNegotiation || + (ptpClock->parentGrants && + ptpClock->parentGrants->grantData[DELAY_RESP_INDEXED].granted)) { + issueDelayReq(rtOpts,ptpClock); + } + } + } else if (ptpClock->portDS.delayMechanism == P2P) { + if (timerExpired(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER])) { + DBGV("event PDELAYREQ_INTERVAL_TIMEOUT_EXPIRES\n"); + /* if unicast negotiation is enabled, only request if granted */ + if(!rtOpts->unicastNegotiation || + ( ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].granted)) { + issuePdelayReq(rtOpts,ptpClock); + } + } + + /* FIXME: Path delay should also rearm its timer with the value received from the Master */ + } + + if (ptpClock->timePropertiesDS.leap59 || ptpClock->timePropertiesDS.leap61) + DBGV("seconds to midnight: %.3f\n",secondsToMidnight()); + + /* leap second period is over */ + if(timerExpired(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER]) && + ptpClock->leapSecondInProgress) { + /* + * do not unpause offset calculation just + * yet, just indicate and it will be + * unpaused in handleAnnounce() + */ + ptpClock->leapSecondPending = FALSE; + timerStop(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER]); + } + /* check if leap second is near and if we should pause updates */ + if( ptpClock->leapSecondPending && + !ptpClock->leapSecondInProgress && + (secondsToMidnight() <= + getPauseAfterMidnight(ptpClock->portDS.logAnnounceInterval, + rtOpts->leapSecondPausePeriod))) { + WARNING("Leap second event imminent - pausing " + "clock and offset updates\n"); + ptpClock->leapSecondInProgress = TRUE; + /* + * start pause timer from now until [pause] after + * midnight, plus an extra second if inserting + * a leap second + */ + timerStart(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER], + ((secondsToMidnight() + + (int)ptpClock->timePropertiesDS.leap61) + + getPauseAfterMidnight(ptpClock->portDS.logAnnounceInterval, + rtOpts->leapSecondPausePeriod))); + } + +/* Update PTP slave statistics from online statistics containers */ +#ifdef PTPD_STATISTICS + if (timerExpired(&ptpClock->timers[STATISTICS_UPDATE_TIMER])) { + if(!rtOpts->enablePanicMode || !ptpClock->panicMode) + updatePtpEngineStats(ptpClock, rtOpts); + } +#endif /* PTPD_STATISTICS */ + + SET_ALARM(ALRM_NO_SYNC, timerExpired(&ptpClock->timers[SYNC_RECEIPT_TIMER])); + SET_ALARM(ALRM_NO_DELAY, timerExpired(&ptpClock->timers[DELAY_RECEIPT_TIMER])); + + break; +#ifndef PTPD_SLAVE_ONLY + case PTP_MASTER: + /* + * handle MASTER timers: + * - Time to send new Announce(s) + * - Time to send new PathDelay + * - Time to send new Sync(s) (last order - so that follow-up always follows sync + * in two-step mode: improves interoperability + * (DelayResp has no timer - as these are sent and retransmitted by the slaves) + */ + + /* master leap second triggers */ + + /* if we have an offset from some source, we assume it's valid */ + if(ptpClock->clockStatus.utcOffset != 0) { + ptpClock->timePropertiesDS.currentUtcOffset = ptpClock->clockStatus.utcOffset; + ptpClock->timePropertiesDS.currentUtcOffsetValid = TRUE; + + } + + /* update the tpDS with clockStatus leap flags - only if running PTP timescale */ + if(ptpClock->timePropertiesDS.ptpTimescale && + (secondsToMidnight() < rtOpts->leapSecondNoticePeriod)) { + ptpClock->timePropertiesDS.leap59 = ptpClock->clockStatus.leapDelete; + ptpClock->timePropertiesDS.leap61 = ptpClock->clockStatus.leapInsert; + } else { + ptpClock->timePropertiesDS.leap59 = FALSE; + ptpClock->timePropertiesDS.leap61 = FALSE; + } + + if(ptpClock->timePropertiesDS.leap59 || + ptpClock->timePropertiesDS.leap61 ) { + if(!ptpClock->leapSecondInProgress) { + ptpClock->leapSecondPending = TRUE; + } + DBGV("seconds to midnight: %.3f\n",secondsToMidnight()); + } + + /* check if leap second is near and if we should pause updates */ + if( ptpClock->leapSecondPending && + !ptpClock->leapSecondInProgress && + (secondsToMidnight() <= + getPauseAfterMidnight(ptpClock->portDS.logAnnounceInterval, + rtOpts->leapSecondPausePeriod))) { + WARNING("Leap second event imminent - pausing " + "event message processing\n"); + ptpClock->leapSecondInProgress = TRUE; + /* + * start pause timer from now until [pause] after + * midnight, plus an extra second if inserting + * a leap second + */ + timerStart(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER], + ((secondsToMidnight() + + (int)ptpClock->timePropertiesDS.leap61) + + getPauseAfterMidnight(ptpClock->portDS.logAnnounceInterval, + rtOpts->leapSecondPausePeriod))); + } + + /* leap second period is over */ + if(timerExpired(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER]) && + ptpClock->leapSecondInProgress) { + ptpClock->leapSecondPending = FALSE; + timerStop(&ptpClock->timers[LEAP_SECOND_PAUSE_TIMER]); + ptpClock->leapSecondInProgress = FALSE; + NOTICE("Leap second event over - resuming " + "event message processing\n"); + } + + + if (timerExpired(&ptpClock->timers[ANNOUNCE_INTERVAL_TIMER])) { + DBGV("event ANNOUNCE_INTERVAL_TIMEOUT_EXPIRES\n"); + /* restart the timer with current interval in case if it changed */ + if(pow(2,ptpClock->portDS.logAnnounceInterval) != ptpClock->timers[ANNOUNCE_INTERVAL_TIMER].interval) { + timerStart(&ptpClock->timers[ANNOUNCE_INTERVAL_TIMER], + pow(2,ptpClock->portDS.logAnnounceInterval)); + } + issueAnnounce(rtOpts, ptpClock); + + } + + if (ptpClock->portDS.delayMechanism == P2P) { + if (timerExpired(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER])) { + DBGV("event PDELAYREQ_INTERVAL_TIMEOUT_EXPIRES\n"); + /* if unicast negotiation is enabled, only request if granted */ + if(!rtOpts->unicastNegotiation || + ( ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].granted)) { + issuePdelayReq(rtOpts,ptpClock); + } + } + } + + if(rtOpts->do_IGMP_refresh && + rtOpts->transport == UDP_IPV4 && + rtOpts->ipMode != IPMODE_UNICAST && + rtOpts->masterRefreshInterval > 9 && + timerExpired(&ptpClock->timers[MASTER_NETREFRESH_TIMER])) { + DBGV("Master state periodic IGMP refresh - next in %d seconds...\n", + rtOpts->masterRefreshInterval); + /* if multicast refresh failed, restart network - helps recover after driver reloads and such */ + if(!netRefreshIGMP(&ptpClock->netPath, rtOpts, ptpClock)) { + WARNING("Error while refreshing multicast - restarting transports\n"); + toState(PTP_FAULTY, rtOpts, ptpClock); + break; + } + } + +// ##### SCORE MODIFICATION BEGIN ##### + Score_PtpBooleanType immediateTimeTransmission = Score_PtpIsImmediateTimeTriggerTrue(); + + if (timerExpired(&ptpClock->timers[SYNC_INTERVAL_TIMER]) + || (SCORE_PTP_TRUE == immediateTimeTransmission)) + { + /* Check if we entered the condition due to the flag or timer */ + if(SCORE_PTP_TRUE == immediateTimeTransmission) + { + /* Reset the Immediate time transmission flag as we are about the transmit the time + on the BUS */ + Score_PtpResetImmediateTimeTrigger(); + } + else + { + DBGV("event SYNC_INTERVAL_TIMEOUT_EXPIRES\n"); + /* re-arm timer if changed */ + // if(pow(2,ptpClock->portDS.logSyncInterval) != ptpClock->timers[SYNC_INTERVAL_TIMER].interval) { + if(((double)(ptpConfig.timebaseConfig.syncPeriodMs)/SCORE_PTP_DEFAULT_SYNC_PERIOD) != ptpClock->timers[SYNC_INTERVAL_TIMER].interval) { + // timerStart(&ptpClock->timers[SYNC_INTERVAL_TIMER], + // pow(2,ptpClock->portDS.logSyncInterval)); + timerStart(&ptpClock->timers[SYNC_INTERVAL_TIMER], + ((double)(ptpConfig.timebaseConfig.syncPeriodMs)/SCORE_PTP_DEFAULT_SYNC_PERIOD)); + } + } +// ##### SCORE MODIFICATION END ##### + + issueSync(rtOpts, ptpClock); + } + if(!ptpClock->warnedUnicastCapacity) { + if(ptpClock->slaveCount >= UNICAST_MAX_DESTINATIONS || + ptpClock->unicastDestinationCount >= UNICAST_MAX_DESTINATIONS) { + if(rtOpts->ipMode == IPMODE_UNICAST) { + WARNING("Maximum unicast slave capacity reached: %d\n",UNICAST_MAX_DESTINATIONS); + ptpClock->warnedUnicastCapacity = TRUE; + } + } + } + + if (timerExpired(&ptpClock->timers[OPERATOR_MESSAGES_TIMER])) { + resetWarnings(rtOpts, ptpClock); + } + + // TODO: why is handle() below expiretimer, while in slave is the opposite + handle(rtOpts, ptpClock); + + if(timerExpired(&ptpClock->timers[DELAY_RECEIPT_TIMER])) { + INFO("NO_DELAY\n"); + } + + if (ptpClock->defaultDS.slaveOnly || ptpClock->defaultDS.clockQuality.clockClass == SLAVE_ONLY_CLOCK_CLASS) + toState(PTP_LISTENING, rtOpts, ptpClock); + + break; +#endif /* PTPD_SLAVE_ONLY */ + + case PTP_DISABLED: + handle(rtOpts, ptpClock); + if(!ptpClock->disabled) { + toState(PTP_LISTENING, rtOpts, ptpClock); + } + break; + + default: + DBG("(doState) do unrecognized state\n"); + break; + } + + if(rtOpts->periodicUpdates && timerExpired(&ptpClock->timers[PERIODIC_INFO_TIMER])) { + periodicUpdate(rtOpts, ptpClock); + } + + if(rtOpts->statusLog.logEnabled && timerExpired(&ptpClock->timers[STATUSFILE_UPDATE_TIMER])) { + writeStatusFile(ptpClock,rtOpts,TRUE); + /* ensures that the current updare interval is used */ + timerStart(&ptpClock->timers[STATUSFILE_UPDATE_TIMER],rtOpts->statusFileUpdateInterval); + } + + if(rtOpts->enablePanicMode && timerExpired(&ptpClock->timers[PANIC_MODE_TIMER])) { + + DBG("Panic check\n"); + + if(--ptpClock->panicModeTimeLeft <= 0) { + ptpClock->panicMode = FALSE; + ptpClock->panicOver = TRUE; + DBG("panic over!\n"); + } + } + +} + +static Boolean +isFromCurrentParent(const PtpClock *ptpClock, const MsgHeader* header) +{ + + return(!memcmp( + ptpClock->parentDS.parentPortIdentity.clockIdentity, + header->sourcePortIdentity.clockIdentity, + CLOCK_IDENTITY_LENGTH) && + (ptpClock->parentDS.parentPortIdentity.portNumber == + header->sourcePortIdentity.portNumber)); + +} + +/* apply any corrections and manglings required to the timestamp */ +static void +timestampCorrection(const RunTimeOpts * rtOpts, PtpClock *ptpClock, TimeInternal *timeStamp) +{ + + TimeInternal fudge = {0,0}; + if(rtOpts->leapSecondHandling == LEAP_SMEAR && (ptpClock->leapSecondPending)) { + DBG("Leap second smear: correction %.09f ns, seconds to midnight %f, leap smear period %d\n", ptpClock->leapSmearFudge, + secondsToMidnight(), rtOpts->leapSecondSmearPeriod); + if(secondsToMidnight() <= rtOpts->leapSecondSmearPeriod) { + ptpClock->leapSmearFudge = 1.0 - (secondsToMidnight() + 0.0) / (rtOpts->leapSecondSmearPeriod+0.0); + if(ptpClock->timePropertiesDS.leap59) { + ptpClock->leapSmearFudge *= -1; + } + fudge = doubleToTimeInternal(ptpClock->leapSmearFudge); + } + } + + if(ptpClock->portDS.portState == PTP_SLAVE && ptpClock->leapSecondPending && !ptpClock->leapSecondInProgress) { + addTime(timeStamp, timeStamp, &fudge); + } + +} + + +void +processMessage(RunTimeOpts* rtOpts, PtpClock* ptpClock, TimeInternal* timeStamp, ssize_t length) +{ + + Boolean isFromSelf = FALSE; + + /* + * make sure we use the TAI to UTC offset specified, if the + * master is sending the UTC_VALID bit + * + * On the slave, all timestamps that we handle here have been + * collected by our local clock (loopback+kernel-level + * timestamp) This includes delayReq just send, and delayResp, + * when it arrives. + * + * these are then adjusted to the same timebase of the Master + * (+35 leap seconds, as of July 2012) + * + * wowczarek: added compatibility flag to always respect the + * announced UTC offset, preventing clock jumps with some GMs + */ + DBGV("__UTC_offset: %d %d \n", ptpClock->timePropertiesDS.currentUtcOffsetValid, ptpClock->timePropertiesDS.currentUtcOffset); + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + timeStamp->seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + + ptpClock->message_activity = TRUE; + if (length < HEADER_LENGTH) { + DBG("Error: message shorter than header length\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + msgUnpackHeader(ptpClock->msgIbuf, &ptpClock->msgTmpHeader); + + /* packet is not from self, and is from a non-zero source address - check ACLs */ + if(ptpClock->netPath.lastSourceAddr && + (ptpClock->netPath.lastSourceAddr != ptpClock->netPath.interfaceAddr.s_addr)) { +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + struct in_addr tmpAddr; + tmpAddr.s_addr = ptpClock->netPath.lastSourceAddr; +#endif /* RUNTIME_DEBUG */ + if(ptpClock->msgTmpHeader.messageType == MANAGEMENT) { + if(rtOpts->managementAclEnabled) { + if (!matchIpv4AccessList( + ptpClock->netPath.managementAcl, + ntohl(ptpClock->netPath.lastSourceAddr))) { + DBG("ACL dropped management message from %s\n", inet_ntoa(tmpAddr)); + ptpClock->counters.aclManagementMessagesDiscarded++; + return; + } else + DBG("ACL Accepted management message from %s\n", inet_ntoa(tmpAddr)); + } + } else if(rtOpts->timingAclEnabled) { + if(!matchIpv4AccessList(ptpClock->netPath.timingAcl, + ntohl(ptpClock->netPath.lastSourceAddr))) { + DBG("ACL dropped timing message from %s\n", inet_ntoa(tmpAddr)); + ptpClock->counters.aclTimingMessagesDiscarded++; + return; + } else + DBG("ACL accepted timing message from %s\n", inet_ntoa(tmpAddr)); + } + } + + if (ptpClock->msgTmpHeader.versionPTP != ptpClock->portDS.versionNumber) { + DBG("ignore version %d message\n", ptpClock->msgTmpHeader.versionPTP); + ptpClock->counters.discardedMessages++; + ptpClock->counters.versionMismatchErrors++; + return; + } + + if(ptpClock->msgTmpHeader.domainNumber != ptpClock->defaultDS.domainNumber) { + Boolean domainOK = FALSE; + int i = 0; + if (rtOpts->unicastNegotiation && ptpClock->unicastDestinationCount) { + for (i = 0; i < ptpClock->unicastDestinationCount; i++) { + if(ptpClock->msgTmpHeader.domainNumber == ptpClock->unicastGrants[i].domainNumber) { + domainOK = TRUE; + DBG("Accepted message type %s from domain %d (unicast neg)\n", + getMessageTypeName(ptpClock->msgTmpHeader.messageType),ptpClock->msgTmpHeader.domainNumber); + break; + } + } + } + if(ptpClock->defaultDS.slaveOnly && rtOpts->anyDomain) { + DBG("anyDomain enabled: accepting announce from domain %d (we are %d)\n", + ptpClock->msgTmpHeader.domainNumber, + ptpClock->defaultDS.domainNumber + ); + } else if(!domainOK) { + DBG("Ignored message %s received from %d domain\n", + getMessageTypeName(ptpClock->msgTmpHeader.messageType), + ptpClock->msgTmpHeader.domainNumber); + ptpClock->portDS.lastMismatchedDomain = ptpClock->msgTmpHeader.domainNumber; + ptpClock->counters.discardedMessages++; + ptpClock->counters.domainMismatchErrors++; + + SET_ALARM(ALRM_DOMAIN_MISMATCH, ((ptpClock->counters.domainMismatchErrors >= DOMAIN_MISMATCH_MIN) && + ptpClock->netPath.receivedPacketsTotal == ptpClock->counters.domainMismatchErrors)); + return; + } + } else { + SET_ALARM(ALRM_DOMAIN_MISMATCH, FALSE); + } + + + if(rtOpts->transport != IEEE_802_3) { + + /* received a UNICAST message */ + if((ptpClock->msgTmpHeader.flagField0 & PTP_UNICAST) == PTP_UNICAST) { + /* in multicast mode, accept only management unicast messages, in hybrid mode accept only unicast delay messages */ + if((rtOpts->ipMode == IPMODE_MULTICAST && ptpClock->msgTmpHeader.messageType != MANAGEMENT) || + (rtOpts->ipMode == IPMODE_HYBRID && ptpClock->msgTmpHeader.messageType != DELAY_REQ && + ptpClock->msgTmpHeader.messageType != DELAY_RESP)) { + DBG("ignored unicast message in multicast mode%d\n"); + ptpClock->counters.discardedMessages++; + return; + } + /* received a MULTICAST message */ + } else { + /* in unicast mode, accept only management multicast messages */ + if(rtOpts->ipMode == IPMODE_UNICAST && ptpClock->msgTmpHeader.messageType != MANAGEMENT) { + DBG("ignored multicast message in unicast mode%d\n"); + ptpClock->counters.discardedMessages++; + return; + } + } + + } + + /* what shall we do with the drunken sailor? */ + timestampCorrection(rtOpts, ptpClock, timeStamp); + + /*Spec 9.5.2.2*/ + // isFromSelf = !cmpPortIdentity(&ptpClock->portDS.portIdentity, &ptpClock->msgTmpHeader.sourcePortIdentity); + + /* + * subtract the inbound latency adjustment if it is not a loop + * back and the time stamp seems reasonable + */ + if (!isFromSelf && timeStamp->seconds > 0) + subTime(timeStamp, timeStamp, &rtOpts->inboundLatency); + + DBG(" ==> %s message received, sequence %d\n", getMessageTypeName(ptpClock->msgTmpHeader.messageType), + ptpClock->msgTmpHeader.sequenceId); + + /* + * on the table below, note that only the event messsages are passed the local time, + * (collected by us by loopback+kernel TS, and adjusted with UTC seconds + * + * (SYNC / DELAY_REQ / PDELAY_REQ / PDELAY_RESP) + */ + switch(ptpClock->msgTmpHeader.messageType) + { + case ANNOUNCE: + handleAnnounce(&ptpClock->msgTmpHeader, + length, isFromSelf, rtOpts, ptpClock); + break; + case SYNC: + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + /* Update the T1 Time - Virtual Local Time Type */ + Score_PtpReadIngressVlt( SCORE_PTP_INGRESS_T1 ); +#endif + handleSync(&ptpClock->msgTmpHeader, + length, timeStamp, isFromSelf, ptpClock->netPath.lastSourceAddr, ptpClock->netPath.lastDestAddr, rtOpts, ptpClock); +// ##### SCORE MODIFICATION END ##### + + break; + case FOLLOW_UP: + handleFollowUp(&ptpClock->msgTmpHeader, + length, isFromSelf, rtOpts, ptpClock); + break; + case DELAY_REQ: + handleDelayReq(&ptpClock->msgTmpHeader, + length, timeStamp, ptpClock->netPath.lastSourceAddr, isFromSelf, rtOpts, ptpClock); + break; + case PDELAY_REQ: + handlePdelayReq(&ptpClock->msgTmpHeader, + length, timeStamp, ptpClock->netPath.lastSourceAddr, isFromSelf, rtOpts, ptpClock); + break; + case DELAY_RESP: + handleDelayResp(&ptpClock->msgTmpHeader, + length, rtOpts, ptpClock); + break; + case PDELAY_RESP: + handlePdelayResp(&ptpClock->msgTmpHeader, + timeStamp, length, isFromSelf, ptpClock->netPath.lastSourceAddr, ptpClock->netPath.lastDestAddr, rtOpts, ptpClock); + break; + case PDELAY_RESP_FOLLOW_UP: + handlePdelayRespFollowUp(&ptpClock->msgTmpHeader, + length, isFromSelf, rtOpts, ptpClock); + break; + case MANAGEMENT: + handleManagement(&ptpClock->msgTmpHeader, + isFromSelf, ptpClock->netPath.lastSourceAddr, rtOpts, ptpClock); + break; + case SIGNALING: + handleSignaling(&ptpClock->msgTmpHeader, isFromSelf, + ptpClock->netPath.lastSourceAddr, rtOpts, ptpClock); + break; + default: + DBG("handle: unrecognized message\n"); + ptpClock->counters.discardedMessages++; + ptpClock->counters.unknownMessages++; + break; + } + + if (rtOpts->displayPackets) + msgDump(ptpClock); + + +} + + +/* check and handle received messages */ +void +handle(RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + int ret; + ssize_t length = -1; + + TimeInternal timeStamp = { 0, 0 }; + fd_set readfds; + + FD_ZERO(&readfds); + if (!ptpClock->message_activity) { + ret = netSelect(NULL, &ptpClock->netPath, &readfds); + if (ret < 0) { + PERROR("failed to poll sockets"); + ptpClock->counters.messageRecvErrors++; + toState(PTP_FAULTY, rtOpts, ptpClock); + return; + } else if (!ret) { + /* DBGV("handle: nothing\n"); */ + return; + } + /* else length > 0 */ + } + + DBG("handle: something\n"); + +#ifdef PTPD_PCAP + if (rtOpts->pcap == TRUE) { + if (ptpClock->netPath.pcapEventSock >=0 && FD_ISSET(ptpClock->netPath.pcapEventSock, &readfds)) { + length = netRecvEvent(ptpClock->msgIbuf, &timeStamp, + &ptpClock->netPath,0); + if (length == 0){ /* timeout, return for now */ + return; + } + + + if (length < 0) { + PERROR("failed to receive event on pcap"); + toState(PTP_FAULTY, rtOpts, ptpClock); + ptpClock->counters.messageRecvErrors++; + + return; + } + if(ptpClock->leapSecondInProgress) { + DBG("Leap second in progress - will not process event message\n"); + } else { + + processMessage(rtOpts, ptpClock, &timeStamp, length); + } + } + if (ptpClock->netPath.pcapGeneralSock >=0 && FD_ISSET(ptpClock->netPath.pcapGeneralSock, &readfds)) { + length = netRecvGeneral(ptpClock->msgIbuf, &ptpClock->netPath); + if (length == 0) /* timeout, return for now */ + return; + if (length < 0) { + PERROR("failed to receive general on pcap"); + toState(PTP_FAULTY, rtOpts, ptpClock); + ptpClock->counters.messageRecvErrors++; + return; + } + processMessage(rtOpts, ptpClock, &timeStamp, length); + } + } else { +#endif + if (FD_ISSET(ptpClock->netPath.eventSock, &readfds)) { + length = netRecvEvent(ptpClock->msgIbuf, &timeStamp, + &ptpClock->netPath, 0); + if (length < 0) { + PERROR("failed to receive on the event socket"); + toState(PTP_FAULTY, rtOpts, ptpClock); + ptpClock->counters.messageRecvErrors++; + return; + } + if(ptpClock->leapSecondInProgress) { + DBG("Leap second in progress - will not process event message\n"); + } else { + processMessage(rtOpts, ptpClock, &timeStamp, length); + } + } + + if (FD_ISSET(ptpClock->netPath.generalSock, &readfds)) { + length = netRecvGeneral(ptpClock->msgIbuf, &ptpClock->netPath); + if (length < 0) { + PERROR("failed to receive on the general socket"); + toState(PTP_FAULTY, rtOpts, ptpClock); + ptpClock->counters.messageRecvErrors++; + return; + } + processMessage(rtOpts, ptpClock, &timeStamp, length); + } +#ifdef PTPD_PCAP + } +#endif + +} + +/*spec 9.5.3*/ +static void +handleAnnounce(MsgHeader *header, ssize_t length, + Boolean isFromSelf, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + UnicastGrantTable *nodeTable = NULL; + UInteger8 localPreference = LOWEST_LOCALPREFERENCE; + + DBGV("HandleAnnounce : Announce message received : \n"); + + if(length < ANNOUNCE_LENGTH) { + DBG("Error: Announce message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + /* if we're ignoring announces (telecom) */ + if(ptpClock->defaultDS.clockQuality.clockClass <= 127 && rtOpts->disableBMCA) { + DBG("unicast master only and disableBMCA: dropping Announce\n"); + ptpClock->counters.discardedMessages++; + return; + } + // ##### SCORE MODIFICATION BEGIN ##### handleAnnounce() +#if SCORE_EXTENSIONS + /* if we're ignoring announces */ + else if( rtOpts->disableBMCA && rtOpts->scoreConfig.autosar ) + { + DBG("disableBMCA && autosar: dropping Announce\n"); + ptpClock->counters.discardedMessages++; + return; + } + else + { + // Just to have the 'else' + } +#endif + // ##### SCORE MODIFICATION END ##### + + if(rtOpts->unicastNegotiation && rtOpts->ipMode == IPMODE_UNICAST) { + + nodeTable = findUnicastGrants(&header->sourcePortIdentity, 0, + ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, + FALSE); + if(nodeTable == NULL || !(nodeTable->grantData[ANNOUNCE_INDEXED].granted)) { + if(!rtOpts->unicastAcceptAny) { + DBG("Ignoring announce from master: unicast transmission not granted\n"); + ptpClock->counters.discardedMessages++; + return; + } + } else { + localPreference = nodeTable->localPreference; + nodeTable->grantData[ANNOUNCE_INDEXED].receiving = header->sequenceId; + } + } + + switch (ptpClock->portDS.portState) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + DBG("Handleannounce : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_UNCALIBRATED: + case PTP_SLAVE: + if (isFromSelf) { + DBGV("HandleAnnounce : Ignore message from self \n"); + return; + } + if(rtOpts->requireUtcValid && !IS_SET(header->flagField1, UTCV)) { + ptpClock->counters.ignoredAnnounce++; + return; + } + + /* + * Valid announce message is received : BMC algorithm + * will be executed + */ + ptpClock->counters.announceMessagesReceived++; + ptpClock->record_update = TRUE; + + switch (isFromCurrentParent(ptpClock, header)) { + case TRUE: + msgUnpackAnnounce(ptpClock->msgIbuf, + &ptpClock->msgTmp.announce); + + /* update datasets (file bmc.c) */ + s1(header,&ptpClock->msgTmp.announce,ptpClock, rtOpts); + + /* update current master in the fmr as well */ + memcpy(&ptpClock->bestMaster->header, + header,sizeof(MsgHeader)); + memcpy(&ptpClock->bestMaster->announce, + &ptpClock->msgTmp.announce,sizeof(MsgAnnounce)); + + if(ptpClock->leapSecondInProgress) { + /* + * if leap second period is over + * (pending == FALSE, inProgress == + * TRUE), unpause offset calculation + * (received first announce after leap + * second) - make sure the upstream knows about this. + */ + if (!ptpClock->leapSecondPending) { + WARNING("Leap second event over - " + "resuming clock and offset updates\n"); + ptpClock->leapSecondInProgress = FALSE; + if(rtOpts->leapSecondHandling==LEAP_STEP) { + ptpClock->clockControl.stepRequired = TRUE; + } + /* reverse frequency shift */ + if(rtOpts->leapSecondHandling==LEAP_SMEAR){ + /* the flags are probably off by now, using the shift sign to detect leap second type */ + if(ptpClock->leapSmearFudge < 0) { + DBG("Reversed LEAP59 smear frequency offset\n"); + ptpClock->servo.observedDrift += 1E9 / rtOpts->leapSecondSmearPeriod; + } + if(ptpClock->leapSmearFudge > 0) { + DBG("Reversed LEAP61 smear frequency offset\n"); + ptpClock->servo.observedDrift -= 1E9 / rtOpts->leapSecondSmearPeriod; + } + ptpClock->leapSmearFudge = 0; + } + ptpClock->timePropertiesDS.leap59 = FALSE; + ptpClock->timePropertiesDS.leap61 = FALSE; + ptpClock->clockStatus.leapInsert = FALSE; + ptpClock->clockStatus.leapDelete = FALSE; + ptpClock->clockStatus.update = TRUE; + + } + } + + DBG2("___ Announce: received Announce from current Master, so reset the Announce timer\n"); + + /* Reset Timer handling Announce receipt timeout - in anyDomain, time out from current domain first */ + /// ##### SCORE MODIFICATION BEGIN ##### timerStart() +#if SCORE_EXTENSIONS + if( !rtOpts->disableBMCA || !rtOpts->scoreConfig.autosar ) +#endif + // ##### SCORE MODIFICATION END ##### + if(header->domainNumber == ptpClock->defaultDS.domainNumber) { + timerStart(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER], + (ptpClock->portDS.announceReceiptTimeout) * + (pow(2,ptpClock->portDS.logAnnounceInterval))); + } +#ifdef PTPD_STATISTICS + if(!timerRunning(&ptpClock->timers[STATISTICS_UPDATE_TIMER])) { + timerStart(&ptpClock->timers[STATISTICS_UPDATE_TIMER], rtOpts->statsUpdateInterval); + } +#endif /* PTPD_STATISTICS */ + + if (rtOpts->announceTimeoutGracePeriod && + ptpClock->announceTimeouts > 0) { + NOTICE("Received Announce message from current master - cancelling timeout\n"); + ptpClock->announceTimeouts = 0; + /* we are available for clock control again */ + ptpClock->clockControl.available = TRUE; + } + + break; + + case FALSE: + /* addForeign takes care of AnnounceUnpacking */ + + /* the actual decision to change masters is + * only done in doState() / record_update == + * TRUE / bmc() + */ + + /* + * wowczarek: do not restart timer here: + * the slave will sit idle if current parent + * is not announcing, but another GM is + */ + addForeign(ptpClock->msgIbuf,header,ptpClock,localPreference,ptpClock->netPath.lastSourceAddr); + break; + + default: + DBG("HandleAnnounce : (isFromCurrentParent)" + "strange value ! \n"); + return; + + } /* switch on (isFromCurrentParent) */ + break; + + /* + * Passive case: previously, this was handled in the default, just like the master case. + * This the announce would call addForeign(), but NOT reset the timer, so after 12s it would expire and we would come alive periodically + * + * This code is now merged with the slave case to reset the timer, and call addForeign() if it's a third master + * + */ + case PTP_PASSIVE: + if (isFromSelf) { + DBGV("HandleAnnounce : Ignore message from self \n"); + return; + } + if(rtOpts->requireUtcValid && !IS_SET(header->flagField1, UTCV)) { + ptpClock->counters.ignoredAnnounce++; + return; + } + /* + * Valid announce message is received : BMC algorithm + * will be executed + */ + ptpClock->counters.announceMessagesReceived++; + ptpClock->record_update = TRUE; + + if (isFromCurrentParent(ptpClock, header)) { + msgUnpackAnnounce(ptpClock->msgIbuf, + &ptpClock->msgTmp.announce); + + /* TODO: not in spec + * datasets should not be updated by another master + * this is the reason why we are PASSIVE and not SLAVE + * this should be p1(ptpClock, rtOpts); + */ + /* update datasets (file bmc.c) */ + s1(header,&ptpClock->msgTmp.announce,ptpClock, rtOpts); + + DBG("___ Announce: received Announce from current Master, so reset the Announce timer\n\n"); + + /* Reset Timer handling Announce receipt timeout: different domain my get here + when using anyDomain.*/ + timerStart(&ptpClock->timers[ANNOUNCE_RECEIPT_TIMER], + (ptpClock->portDS.announceReceiptTimeout) * + (pow(2,ptpClock->portDS.logAnnounceInterval))); + + } else { + /*addForeign takes care of AnnounceUnpacking*/ + /* the actual decision to change masters is only done in doState() / record_update == TRUE / bmc() */ + /* the original code always called: addforeign(new master) + timerstart(announce) */ + + DBG("___ Announce: received Announce from another master, will add to the list, as it might be better\n\n"); + DBGV("this is to be decided immediatly by bmc())\n\n"); + addForeign(ptpClock->msgIbuf,header,ptpClock,localPreference,ptpClock->netPath.lastSourceAddr); + } + break; + + + case PTP_MASTER: + case PTP_LISTENING: /* listening mode still causes timeouts in order to send IGMP refreshes */ + default : + if (isFromSelf) { + DBGV("HandleAnnounce : Ignore message from self \n"); + return; + } + if(rtOpts->requireUtcValid && !IS_SET(header->flagField1, UTCV)) { + ptpClock->counters.ignoredAnnounce++; + return; + } + ptpClock->counters.announceMessagesReceived++; + DBGV("Announce message from another foreign master\n"); + addForeign(ptpClock->msgIbuf,header,ptpClock, localPreference,ptpClock->netPath.lastSourceAddr); + ptpClock->record_update = TRUE; /* run BMC() as soon as possible */ + break; + + } /* switch on (port_state) */ +} + + +static void +handleSync(const MsgHeader *header, ssize_t length, + TimeInternal *tint, Boolean isFromSelf, Integer32 sourceAddress, Integer32 destinationAddress, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + TimeInternal OriginTimestamp; + TimeInternal correctionField; + + Integer32 dst = 0; + + if((rtOpts->ipMode == IPMODE_UNICAST) && destinationAddress) { + dst = destinationAddress; + } else { + dst = 0; + } + + DBGV("Sync message received : \n"); + + if (length < SYNC_LENGTH) { + DBG("Error: Sync message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + if(!isFromSelf && rtOpts->unicastNegotiation && rtOpts->ipMode == IPMODE_UNICAST) { + UnicastGrantTable *nodeTable = NULL; + nodeTable = findUnicastGrants(&header->sourcePortIdentity, 0, + ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, + FALSE); + if(nodeTable != NULL) { + nodeTable->grantData[SYNC_INDEXED].receiving = header->sequenceId; + } + } + + switch (ptpClock->portDS.portState) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_PASSIVE: + case PTP_LISTENING: + // ##### SCORE MODIFICATION BEGIN ##### handleAnnounce() +#if SCORE_EXTENSIONS + // Check if we received a SYNC message from a foreign master + if( !isFromSelf && !isFromCurrentParent(ptpClock, header) ) + { + DBGV("Sync message from another foreign master\n"); + + UInteger8 localPreference = LOWEST_LOCALPREFERENCE; + + addForeign(ptpClock->msgIbuf, header, ptpClock, localPreference, ptpClock->netPath.lastSourceAddr); + ptpClock->record_update = TRUE; /* run BMC() as soon as possible */ + + break; + } +#endif + // ##### SCORE MODIFICATION END ##### + + DBGV("HandleSync : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_UNCALIBRATED: + case PTP_SLAVE: + if (isFromSelf) { + DBGV("HandleSync: Ignore message from self \n"); + return; + } + + if (isFromCurrentParent(ptpClock, header)) { + ptpClock->counters.syncMessagesReceived++; + timerStart(&ptpClock->timers[SYNC_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logSyncInterval)) + )); + + /* We only start our own delayReq timer after receiving the first sync */ + if (ptpClock->syncWaiting) { + ptpClock->syncWaiting = FALSE; + + NOTICE("Received first Sync from Master\n"); + + if (ptpClock->portDS.delayMechanism == E2E) + timerStart(&ptpClock->timers[DELAYREQ_INTERVAL_TIMER], + pow(2, ptpClock->portDS.logMinDelayReqInterval)); + else if (ptpClock->portDS.delayMechanism == P2P) + timerStart(&ptpClock->timers[PDELAYREQ_INTERVAL_TIMER], + pow(2, ptpClock->portDS.logMinPdelayReqInterval)); + } +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + /* Do not perform sequence counter check for the first time. + When we get to this else if, it means it is no longer first sync */ + else if (ptpConfig.timebaseConfig.globalTimeSequenceCounterJumpWidth) { + uint16_t currentJumpWidth = (header->sequenceId > ptpClock->prevSyncSequenceId) + ? (header->sequenceId - ptpClock->prevSyncSequenceId) + : ((header->sequenceId + SCORE_PTP_SEQUENCE_ID_MAX_VALUE) - ptpClock->prevSyncSequenceId); + + if (currentJumpWidth > ptpConfig.timebaseConfig.globalTimeSequenceCounterJumpWidth) { + ptpClock->prevSyncSequenceId = header->sequenceId; + WARNING( + "The current sequence id jump width (%d) is greater than configured value (%d). Rejected the sync message.\n", + currentJumpWidth, ptpConfig.timebaseConfig.globalTimeSequenceCounterJumpWidth); + break; + } + } +#endif +// ##### SCORE MODIFICATION END ##### + + ptpClock->portDS.logSyncInterval = header->logMessageInterval; + + /* this will be 0x7F for unicast so if we have a grant, use the granted value */ + if(rtOpts->unicastNegotiation && ptpClock->parentGrants + && ptpClock->parentGrants->grantData[SYNC_INDEXED].granted) { + ptpClock->portDS.logSyncInterval = ptpClock->parentGrants->grantData[SYNC_INDEXED].logInterval; + } + + + recordSync(header->sequenceId, tint); + + if ((header->flagField0 & PTP_TWO_STEP) == PTP_TWO_STEP) { + DBG2("HandleSync: waiting for follow-up \n"); + ptpClock->defaultDS.twoStepFlag=TRUE; +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + // After the sync is received, start the followup-receipt timer + timerStart(&ptpClock->timers[FOLLOWUP_RECEIPT_TIMER], + ((double) ptpConfig.timebaseConfig.followUpTimeoutMs)/1000); +#endif +// ##### SCORE MODIFICATION END ##### + if(ptpClock->waitingForFollow) { + ptpClock->followUpGap++; + if(ptpClock->followUpGap < MAX_FOLLOWUP_GAP) { + DBG("Received Sync while waiting for follow-up - " + "will wait for another %d messages\n", + MAX_FOLLOWUP_GAP - ptpClock->followUpGap); + break; + } + DBG("No follow-up for %d sync - waiting for new follow-up\n", + ptpClock->followUpGap); + } + + ptpClock->sync_receive_time.seconds = tint->seconds; + ptpClock->sync_receive_time.nanoseconds = tint->nanoseconds; + + ptpClock->waitingForFollow = TRUE; + /*Save correctionField of Sync message*/ + integer64_to_internalTime( + header->correctionField, + &correctionField); + ptpClock->lastSyncCorrectionField.seconds = + correctionField.seconds; + ptpClock->lastSyncCorrectionField.nanoseconds = + correctionField.nanoseconds; + ptpClock->recvSyncSequenceId = + header->sequenceId; + ptpClock->prevSyncSequenceId = + header->sequenceId; + + break; + } else { + + ptpClock->sync_receive_time.seconds = tint->seconds; + ptpClock->sync_receive_time.nanoseconds = tint->nanoseconds; + + ptpClock->recvSyncSequenceId = + header->sequenceId; + msgUnpackSync(ptpClock->msgIbuf, + &ptpClock->msgTmp.sync); + integer64_to_internalTime( + ptpClock->msgTmpHeader.correctionField, + &correctionField); + timeInternal_display(&correctionField); + ptpClock->waitingForFollow = FALSE; + toInternalTime(&OriginTimestamp, + &ptpClock->msgTmp.sync.originTimestamp); + updateOffset(&OriginTimestamp, + &ptpClock->sync_receive_time, + &ptpClock->ofm_filt,rtOpts, + ptpClock,&correctionField); + checkOffset(rtOpts,ptpClock); + if (ptpClock->clockControl.updateOK) { + ptpClock->acceptedUpdates++; + updateClock(rtOpts,ptpClock); + } + ptpClock->offsetUpdates++; + + ptpClock->defaultDS.twoStepFlag=FALSE; + break; + } + } else { + DBG("HandleSync: Sync message received from " + "another Master not our own \n"); + ptpClock->counters.discardedMessages++; + } + break; + + case PTP_MASTER: + default : + if (!isFromSelf) { + DBGV("HandleSync: Sync message received from " + "another Master \n"); + /* we are the master, but another is sending */ + ptpClock->counters.discardedMessages++; + break; + } if (ptpClock->defaultDS.twoStepFlag) { + DBGV("HandleSync: going to send followup message\n"); + + /* who do we send the followUp to? no destination given - try looking up index */ + if((rtOpts->ipMode == IPMODE_UNICAST) && !dst) { + msgUnpackSync(ptpClock->msgIbuf, + &ptpClock->msgTmp.sync); + toInternalTime(&OriginTimestamp, &ptpClock->msgTmp.sync.originTimestamp); + dst = lookupSyncIndex(&OriginTimestamp, header->sequenceId, ptpClock->syncDestIndex); + +#ifdef RUNTIME_DEBUG + { + struct in_addr tmpAddr; + tmpAddr.s_addr = dst; + DBG("handleSync: master sync dest cache hit: %s\n", inet_ntoa(tmpAddr)); + } +#endif /* RUNTIME_DEBUG */ + + if(!dst) { + DBG("handleSync: master sync dest cache miss - searching\n"); + dst = findSyncDestination(&OriginTimestamp, rtOpts, ptpClock); + /* give up. Better than sending FollowUp to random destinations*/ + if(!dst) { + DBG("handleSync: master sync dest not found for followUp. Giving up.\n"); + return; + } else { + DBG("unicast destination found.\n"); + } + } + + } + +#ifndef PTPD_SLAVE_ONLY /* does not get compiled when building slave only */ + processSyncFromSelf(tint, rtOpts, ptpClock, dst, header->sequenceId); +#endif /* PTPD_SLAVE_ONLY */ + break; + } else { + DBGV("HandleSync: Sync message received from self\n"); + } + } +} + +#ifndef PTPD_SLAVE_ONLY /* does not get compiled when building slave only */ +static void +processSyncFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock, Integer32 dst, const UInteger16 sequenceId) { + TimeInternal timestamp; + /*Add latency*/ + +// ##### SCORE MODIFICATION BEGIN ##### + +#ifndef SCORE_EXTENSIONS + addTime(×tamp, tint, &rtOpts->outboundLatency); +#endif + +// ##### SCORE MODIFICATION END ##### + + /* Issue follow-up CORRESPONDING TO THIS SYNC */ + issueFollowup(×tamp, rtOpts, ptpClock, dst, sequenceId); +} +#endif /* PTPD_SLAVE_ONLY */ + +static void +handleFollowUp(const MsgHeader *header, ssize_t length, + Boolean isFromSelf, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + TimeInternal preciseOriginTimestamp; + TimeInternal correctionField; + + DBGV("Handlefollowup : Follow up message received \n"); + + if (length < ptpConfig.configGlobals.followupLength) + { +// ##### SCORE MODIFICATION BEGIN ##### + fprintf(stderr,"[ERROR] score_aptpd2 : Follow up message received is too short. Expected length should be atleast %d bytes.\n"\ + "Hint: Check if the Master and Slave are following the same message format. (ECU Configuration parameter - messageCompliance). \n", ptpConfig.configGlobals.followupLength); +// ##### SCORE MODIFICATION END ##### + ptpClock->counters.messageFormatErrors++; + return; + } + + if (isFromSelf) + { + DBGV("Handlefollowup : Ignore message from self \n"); + return; + } + + switch (ptpClock->portDS.portState) + { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_LISTENING: + DBGV("Handfollowup : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_UNCALIBRATED: + case PTP_SLAVE: + if (isFromCurrentParent(ptpClock, header)) { + ptpClock->counters.followUpMessagesReceived++; + ptpClock->portDS.logSyncInterval = header->logMessageInterval; + /* this will be 0x7F for unicast so if we have a grant, use the granted value */ + if(rtOpts->unicastNegotiation && ptpClock->parentGrants + && ptpClock->parentGrants->grantData[SYNC_INDEXED].granted) { + ptpClock->portDS.logSyncInterval = ptpClock->parentGrants->grantData[SYNC_INDEXED].logInterval; + } + + if (ptpClock->waitingForFollow) { + if (ptpClock->recvSyncSequenceId == + header->sequenceId) { +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + if(!timerExpired(&ptpClock->timers[FOLLOWUP_RECEIPT_TIMER])) { + timerStop(&ptpClock->timers[FOLLOWUP_RECEIPT_TIMER]); +#endif +// ##### SCORE MODIFICATION END ##### + + ptpClock->followUpGap = 0; + msgUnpackFollowUp(ptpClock->msgIbuf, + &ptpClock->msgTmp.follow); + ptpClock->waitingForFollow = FALSE; + toInternalTime(&preciseOriginTimestamp, + &ptpClock->msgTmp.follow.preciseOriginTimestamp); + integer64_to_internalTime(ptpClock->msgTmpHeader.correctionField, + &correctionField); + + addTime(&correctionField, &correctionField, &ptpClock->lastSyncCorrectionField); + +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + Timestamp correctionFieldTimeStamp = {0, 0}; + fromInternalTime(&correctionField, &correctionFieldTimeStamp); + + updateOffset(&preciseOriginTimestamp, &ptpClock->sync_receive_time, &ptpClock->ofm_filt, rtOpts, + ptpClock, &correctionField); + checkOffset(rtOpts, ptpClock); + if (ptpClock->clockControl.updateOK) { + ptpClock->acceptedUpdates++; + updateClock(rtOpts, ptpClock); + } + ptpClock->offsetUpdates++; + +#if PTPD_SCORE_DEBUG + printf("[DEBUG] aptpd2: Received packet now...\n"); + for (int i = 0; i < ptpConfig.configGlobals.followupLength; i++) { + printf("(%d:%02X) ", i, (unsigned char)(ptpClock->msgIbuf[i])); + if (0 == (i % 10)) { + printf("\n"); + } + } +#endif + Score_PtpExtractFupHeaderInfo((void *)header, SCORE_PTP_FALSE); + + Score_PtpReadFupInfoFromBuffer((uint8_t *)&ptpClock->msgTmp.follow.preciseOriginTimestamp, + (uint8_t *)&correctionFieldTimeStamp, + (uint8_t *)(ptpClock->msgIbuf + SCORE_PTP_FUP_INFO_AUTOSAR_START_IDX)); +#endif + +// ##### SCORE MODIFICATION END ##### + break; +// ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + } + else { + ptpClock->waitingForFollow = FALSE; + ptpClock->counters.discardedMessages++; + } +#endif +// ##### SCORE MODIFICATION END ##### + } else { + DBG("HandleFollowUp : sequence mismatch - " + "last Sync: %d, this FollowUp: %d\n", + ptpClock->recvSyncSequenceId, + header->sequenceId); + ptpClock->counters.sequenceMismatchErrors++; + ptpClock->counters.discardedMessages++; + } + } else { + DBG2("Ignored followup, Slave was not waiting a follow up " + "message \n"); + ptpClock->counters.discardedMessages++; + } + } else { + DBG2("Ignored, Follow up message is not from current parent \n"); + ptpClock->counters.discardedMessages++; + } + + case PTP_MASTER: + case PTP_PASSIVE: + if(isFromCurrentParent(ptpClock, header)) + DBGV("Ignored, Follow up message received from current master \n"); + else { + /* follow-ups and syncs are expected to be seen from parent, but not from others */ + DBGV("Follow up message received from foreign master!\n"); + ptpClock->counters.discardedMessages++; + } + break; + + default: + DBG("do unrecognized state1\n"); + ptpClock->counters.discardedMessages++; + break; + } /* Switch on (port_state) */ + +} + +void +handleDelayReq(const MsgHeader *header, ssize_t length, + const TimeInternal *tint, Integer32 sourceAddress, Boolean isFromSelf, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + UnicastGrantTable *nodeTable = NULL; + + if (ptpClock->portDS.delayMechanism == E2E) { + + if(!isFromSelf && rtOpts->unicastNegotiation && rtOpts->ipMode == IPMODE_UNICAST) { + nodeTable = findUnicastGrants(&header->sourcePortIdentity, 0, + ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, + FALSE); + if(nodeTable == NULL || !(nodeTable->grantData[DELAY_RESP_INDEXED].granted)) { + DBG("Ignoring Delay Request from slave: unicast transmission not granted\n"); + ptpClock->counters.discardedMessages++; + return; + } else { + nodeTable->grantData[DELAY_REQ].receiving = header->sequenceId; + } + + } + + + DBG("delayReq message received : \n"); + + if (length < DELAY_REQ_LENGTH) { + DBG("Error: DelayReq message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + switch (ptpClock->portDS.portState) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_UNCALIBRATED: + case PTP_LISTENING: + case PTP_PASSIVE: + DBGV("HandledelayReq : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_SLAVE: + if (isFromSelf) { + DBG("==> Handle DelayReq (%d)\n", + header->sequenceId); + if ( ((UInteger16)(header->sequenceId + 1)) != + ptpClock->sentDelayReqSequenceId) { + DBG("HandledelayReq : sequence mismatch - " + "last DelayReq sent: %d, received: %d\n", + ptpClock->sentDelayReqSequenceId, + header->sequenceId + ); + ptpClock->counters.discardedMessages++; + ptpClock->counters.sequenceMismatchErrors++; + break; + } + + /* + * Get sending timestamp from IP stack + * with SO_TIMESTAMP + */ + + /* + * Make sure we process the REQ + * _before_ the RESP. While we could + * do this by any order, (because + * it's implicitly indexed by + * (ptpClock->sentDelayReqSequenceId + * - 1), this is now made explicit + */ + processDelayReqFromSelf(tint, rtOpts, ptpClock); + + break; + } else { + DBG2("HandledelayReq : disregard delayreq from other client\n"); + ptpClock->counters.discardedMessages++; + } + break; + + case PTP_MASTER: + msgUnpackHeader(ptpClock->msgIbuf, + &ptpClock->delayReqHeader); + ptpClock->counters.delayReqMessagesReceived++; + + issueDelayResp(tint,&ptpClock->delayReqHeader, sourceAddress, + rtOpts,ptpClock); + break; + + default: + DBG("do unrecognized state2\n"); + ptpClock->counters.discardedMessages++; + break; + } + } else if (ptpClock->portDS.delayMechanism == P2P) {/* (Peer to Peer mode) */ + DBG("Delay messages are ignored in Peer to Peer mode\n"); + ptpClock->counters.discardedMessages++; + ptpClock->counters.delayMechanismMismatchErrors++; + /* no delay mechanism */ + } else { + DBG("DelayReq ignored - we are in DELAY_DISABLED mode"); + ptpClock->counters.discardedMessages++; + } +} + + +static void +processDelayReqFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock) { + + + ptpClock->waitingForDelayResp = TRUE; + + ptpClock->delay_req_send_time.seconds = tint->seconds; + ptpClock->delay_req_send_time.nanoseconds = tint->nanoseconds; + + /*Add latency*/ + addTime(&ptpClock->delay_req_send_time, + &ptpClock->delay_req_send_time, + &rtOpts->outboundLatency); + + DBGV("processDelayReqFromSelf: %s %d\n", + dump_TimeInternal(&ptpClock->delay_req_send_time), + rtOpts->outboundLatency); + +} + +static void +handleDelayResp(const MsgHeader *header, ssize_t length, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + if (ptpClock->portDS.delayMechanism == E2E) { + + + TimeInternal requestReceiptTimestamp; + TimeInternal correctionField; + + if(rtOpts->unicastNegotiation && rtOpts->ipMode == IPMODE_UNICAST) { + UnicastGrantTable *nodeTable = NULL; + nodeTable = findUnicastGrants(&header->sourcePortIdentity, 0, + ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, + FALSE); + if(nodeTable != NULL) { + nodeTable->grantData[DELAY_RESP_INDEXED].receiving = header->sequenceId; + } else { + DBG("delayResp - unicast master not identified\n"); + } + } + + DBGV("delayResp message received : \n"); + + if(length < DELAY_RESP_LENGTH) { + DBG("Error: DelayResp message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + switch(ptpClock->portDS.portState) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_UNCALIBRATED: + case PTP_LISTENING: + DBGV("HandledelayResp : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + case PTP_SLAVE: + msgUnpackDelayResp(ptpClock->msgIbuf, + &ptpClock->msgTmp.resp); + + if ((memcmp(ptpClock->portDS.portIdentity.clockIdentity, + ptpClock->msgTmp.resp.requestingPortIdentity.clockIdentity, + CLOCK_IDENTITY_LENGTH) == 0) && + (ptpClock->portDS.portIdentity.portNumber == + ptpClock->msgTmp.resp.requestingPortIdentity.portNumber) + && isFromCurrentParent(ptpClock, header)) { + DBG("==> Handle DelayResp (%d)\n", + header->sequenceId); + + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinDelayReqInterval)))); + + if (!ptpClock->waitingForDelayResp) { + DBG("Ignored DelayResp sequence %d - wasn't waiting for one\n", + header->sequenceId); + ptpClock->counters.discardedMessages++; + break; + } + + if (ptpClock->sentDelayReqSequenceId != + ((UInteger16)(header->sequenceId + 1))) { + DBG("HandledelayResp : sequence mismatch - " + "last DelayReq sent: %d, delayResp received: %d\n", + ptpClock->sentDelayReqSequenceId, + header->sequenceId + ); + ptpClock->counters.discardedMessages++; + ptpClock->counters.sequenceMismatchErrors++; + break; + } + + ptpClock->counters.delayRespMessagesReceived++; + ptpClock->waitingForDelayResp = FALSE; + + toInternalTime(&requestReceiptTimestamp, + &ptpClock->msgTmp.resp.receiveTimestamp); + ptpClock->delay_req_receive_time.seconds = + requestReceiptTimestamp.seconds; + ptpClock->delay_req_receive_time.nanoseconds = + requestReceiptTimestamp.nanoseconds; + + integer64_to_internalTime( + header->correctionField, + &correctionField); + /* + send_time = delay_req_send_time (received as CMSG in handleEvent) + recv_time = requestReceiptTimestamp (received inside delayResp) + */ + + updateDelay(&ptpClock->mpd_filt, + rtOpts,ptpClock, &correctionField); + if (ptpClock->delayRespWaiting) { + + NOTICE("Received first Delay Response from Master\n"); + } + + if (rtOpts->ignore_delayreq_interval_master == 0) { + DBGV("current delay_req: %d new delay req: %d \n", + ptpClock->portDS.logMinDelayReqInterval, + header->logMessageInterval); + if (ptpClock->portDS.logMinDelayReqInterval != header->logMessageInterval) { + + if(header->logMessageInterval == UNICAST_MESSAGEINTERVAL && + rtOpts->autoDelayReqInterval) { + + if(rtOpts->unicastNegotiation && ptpClock->parentGrants && ptpClock->parentGrants->grantData[DELAY_RESP_INDEXED].granted) { + if(ptpClock->delayRespWaiting) { + NOTICE("Received Delay Interval %d from master\n", + ptpClock->parentGrants->grantData[DELAY_RESP_INDEXED].logInterval); + } + ptpClock->portDS.logMinDelayReqInterval = ptpClock->parentGrants->grantData[DELAY_RESP_INDEXED].logInterval; + } else { + + if(ptpClock->delayRespWaiting) { + NOTICE("Received Delay Interval %d from master (unicast-unknown) - overriding with %d\n", + header->logMessageInterval, rtOpts->logMinDelayReqInterval); + } + ptpClock->portDS.logMinDelayReqInterval = rtOpts->logMinDelayReqInterval; + + } + } else { + /* Accept new DelayReq value from the Master */ + + NOTICE("Received new Delay Request interval %d from Master (was: %d)\n", + header->logMessageInterval, ptpClock->portDS.logMinDelayReqInterval ); + + // collect new value indicated by the Master + ptpClock->portDS.logMinDelayReqInterval = header->logMessageInterval; + } + /* FIXME: the actual rearming of this timer with the new value only happens later in doState()/issueDelayReq() */ + } + } else { + + if (ptpClock->portDS.logMinDelayReqInterval != rtOpts->logMinDelayReqInterval) { + INFO("New Delay Request interval applied: %d (was: %d)\n", + rtOpts->logMinDelayReqInterval, ptpClock->portDS.logMinDelayReqInterval); + } + ptpClock->portDS.logMinDelayReqInterval = rtOpts->logMinDelayReqInterval; + } + /* arm the timer again now that we have the correct delayreq interval */ + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinDelayReqInterval)))); + } else { + + DBG("HandledelayResp : delayResp doesn't match with the delayReq. \n"); + ptpClock->counters.discardedMessages++; + break; + } + + ptpClock->delayRespWaiting = FALSE; + } + } else if (ptpClock->portDS.delayMechanism == P2P) { /* (Peer to Peer mode) */ + DBG("Delay messages are disregarded in Peer to Peer mode \n"); + ptpClock->counters.discardedMessages++; + ptpClock->counters.delayMechanismMismatchErrors++; + /* no delay mechanism */ + } else { + DBG("DelayResp ignored - we are in DELAY_DISABLED mode"); + ptpClock->counters.discardedMessages++; + } + +} + + +static void +handlePdelayReq(MsgHeader *header, ssize_t length, + const TimeInternal *tint, Integer32 sourceAddress, Boolean isFromSelf, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + UnicastGrantTable *nodeTable = NULL; + + if (ptpClock->portDS.delayMechanism == P2P) { + + if(!isFromSelf && rtOpts->unicastNegotiation && rtOpts->ipMode == IPMODE_UNICAST) { + nodeTable = findUnicastGrants(&header->sourcePortIdentity, 0, + ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, + FALSE); + if(nodeTable == NULL || !(nodeTable->grantData[PDELAY_RESP_INDEXED].granted)) { + DBG("Ignoring Peer Delay Request from peer: unicast transmission not granted\n"); + ptpClock->counters.discardedMessages++; + return; + } else { + nodeTable->grantData[PDELAY_RESP_INDEXED].receiving = header->sequenceId; + } + + } + + DBGV("PdelayReq message received : \n"); + + if(length < PDELAY_REQ_LENGTH) { + DBG("Error: PdelayReq message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + switch (ptpClock->portDS.portState ) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_UNCALIBRATED: + case PTP_LISTENING: + DBGV("HandlePdelayReq : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_SLAVE: + case PTP_MASTER: + case PTP_PASSIVE: + + if (isFromSelf) { + processPdelayReqFromSelf(tint, rtOpts, ptpClock); + break; + } else { + ptpClock->counters.pdelayReqMessagesReceived++; + msgUnpackHeader(ptpClock->msgIbuf, + &ptpClock->PdelayReqHeader); + issuePdelayResp(tint, header, sourceAddress, rtOpts, + ptpClock); + break; + } + default: + DBG("do unrecognized state3\n"); + ptpClock->counters.discardedMessages++; + break; + } + } else if (ptpClock->portDS.delayMechanism == E2E){/* (End to End mode..) */ + DBG("Peer Delay messages are disregarded in End to End " + "mode \n"); + ptpClock->counters.discardedMessages++; + ptpClock->counters.delayMechanismMismatchErrors++; + /* no delay mechanism */ + } else { + DBG("PdelayReq ignored - we are in DELAY_DISABLED mode"); + ptpClock->counters.discardedMessages++; + } +} + +static void +processPdelayReqFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock) { + /* + * Get sending timestamp from IP stack + * with SO_TIMESTAMP + */ + ptpClock->pdelay_req_send_time.seconds = tint->seconds; + ptpClock->pdelay_req_send_time.nanoseconds = tint->nanoseconds; + + /*Add latency*/ + addTime(&ptpClock->pdelay_req_send_time, + &ptpClock->pdelay_req_send_time, + &rtOpts->outboundLatency); +} + +static void +handlePdelayResp(const MsgHeader *header, TimeInternal *tint, + ssize_t length, Boolean isFromSelf, Integer32 sourceAddress, Integer32 destinationAddress, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + if (ptpClock->portDS.delayMechanism == P2P) { + + Integer32 dst = 0; + + if(destinationAddress) { + dst = destinationAddress; + } else { + /* last resort: may cause PdelayRespFollowUp to be sent to incorrect destinations */ + dst = ptpClock->lastPdelayRespDst; + } + + + /* Boolean isFromCurrentParent = FALSE; NOTE: This is never used in this function */ + TimeInternal requestReceiptTimestamp; + TimeInternal correctionField; + + DBG("PdelayResp message received : \n"); + + if (length < PDELAY_RESP_LENGTH) { + DBG("Error: PdelayResp message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + switch (ptpClock->portDS.portState ) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_UNCALIBRATED: + case PTP_LISTENING: + DBGV("HandlePdelayResp : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_SLAVE: + case PTP_MASTER: + if (ptpClock->defaultDS.twoStepFlag && isFromSelf) { + processPdelayRespFromSelf(tint, rtOpts, ptpClock, dst, header->sequenceId); + break; + } + msgUnpackPdelayResp(ptpClock->msgIbuf, + &ptpClock->msgTmp.presp); + + if (ptpClock->sentPdelayReqSequenceId != + ((UInteger16)(header->sequenceId + 1))) { + DBG("PdelayResp: sequence mismatch - sent: %d, received: %d\n", + ptpClock->sentPdelayReqSequenceId, + header->sequenceId); + ptpClock->counters.discardedMessages++; + ptpClock->counters.sequenceMismatchErrors++; + break; + } + if ((!memcmp(ptpClock->portDS.portIdentity.clockIdentity,ptpClock->msgTmp.presp.requestingPortIdentity.clockIdentity,CLOCK_IDENTITY_LENGTH)) + && ( ptpClock->portDS.portIdentity.portNumber == ptpClock->msgTmp.presp.requestingPortIdentity.portNumber)) { + ptpClock->counters.pdelayRespMessagesReceived++; + /* Two Step Clock */ + if ((header->flagField0 & PTP_TWO_STEP) == PTP_TWO_STEP) { + /*Store t4 (Fig 35)*/ + ptpClock->pdelay_resp_receive_time.seconds = tint->seconds; + ptpClock->pdelay_resp_receive_time.nanoseconds = tint->nanoseconds; + /*store t2 (Fig 35)*/ + toInternalTime(&requestReceiptTimestamp, + &ptpClock->msgTmp.presp.requestReceiptTimestamp); + ptpClock->pdelay_req_receive_time.seconds = requestReceiptTimestamp.seconds; + ptpClock->pdelay_req_receive_time.nanoseconds = requestReceiptTimestamp.nanoseconds; + + integer64_to_internalTime(header->correctionField,&correctionField); + ptpClock->lastPdelayRespCorrectionField.seconds = correctionField.seconds; + ptpClock->lastPdelayRespCorrectionField.nanoseconds = correctionField.nanoseconds; + } else { + /* One step Clock */ + /*Store t4 (Fig 35)*/ + ptpClock->pdelay_resp_receive_time.seconds = tint->seconds; + ptpClock->pdelay_resp_receive_time.nanoseconds = tint->nanoseconds; + + integer64_to_internalTime(header->correctionField,&correctionField); + updatePeerDelay (&ptpClock->mpd_filt,rtOpts,ptpClock,&correctionField,FALSE); + if (rtOpts->ignore_delayreq_interval_master == 0) { + DBGV("current pdelay_req: %d new pdelay req: %d \n", + ptpClock->portDS.logMinPdelayReqInterval, + header->logMessageInterval); + if (ptpClock->portDS.logMinPdelayReqInterval != header->logMessageInterval) { + + if(header->logMessageInterval == UNICAST_MESSAGEINTERVAL && + rtOpts->autoDelayReqInterval) { + + if(rtOpts->unicastNegotiation && ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].granted) { + if(ptpClock->delayRespWaiting) { + NOTICE("Received Peer Delay Interval %d from peer\n", + ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].logInterval); + } + ptpClock->portDS.logMinPdelayReqInterval = ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].logInterval; + } else { + + if(ptpClock->delayRespWaiting) { + NOTICE("Received Peer Delay Interval %d from peer (unicast-unknown) - overriding with %d\n", + header->logMessageInterval, rtOpts->logMinPdelayReqInterval); + } + ptpClock->portDS.logMinPdelayReqInterval = rtOpts->logMinPdelayReqInterval; + + } + } else { + /* Accept new DelayReq value from the Master */ + + NOTICE("Received new Peer Delay Request interval %d from Master (was: %d)\n", + header->logMessageInterval, ptpClock->portDS.logMinPdelayReqInterval ); + + // collect new value indicated by the Master + ptpClock->portDS.logMinPdelayReqInterval = header->logMessageInterval; + } + } else { + + if (ptpClock->portDS.logMinPdelayReqInterval != rtOpts->logMinPdelayReqInterval) { + INFO("New Peer Delay Request interval applied: %d (was: %d)\n", + rtOpts->logMinPdelayReqInterval, ptpClock->portDS.logMinPdelayReqInterval); + } + ptpClock->portDS.logMinPdelayReqInterval = rtOpts->logMinPdelayReqInterval; + } + + } + ptpClock->delayRespWaiting = FALSE; + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinPdelayReqInterval)))); + } + + ptpClock->recvPdelayRespSequenceId = header->sequenceId; + break; + } else { + DBG("HandlePdelayResp : Pdelayresp doesn't " + "match with the PdelayReq. \n"); + ptpClock->counters.discardedMessages++; + break; + } + break; /* XXX added by gnn for safety */ + default: + DBG("do unrecognized state4\n"); + ptpClock->counters.discardedMessages++; + break; + } + } else if (ptpClock->portDS.delayMechanism == E2E) { /* (End to End mode..) */ + DBG("Peer Delay messages are disregarded in End to End " + "mode \n"); + ptpClock->counters.discardedMessages++; + ptpClock->counters.delayMechanismMismatchErrors++; + /* no delay mechanism */ + } else { + DBG("PdelayResp ignored - we are in DELAY_DISABLED mode"); + ptpClock->counters.discardedMessages++; + } +} + +static void +processPdelayRespFromSelf(const TimeInternal * tint, const RunTimeOpts * rtOpts, PtpClock * ptpClock, Integer32 dst, const UInteger16 sequenceId) +{ + TimeInternal timestamp; + + addTime(×tamp, tint, &rtOpts->outboundLatency); + + issuePdelayRespFollowUp(×tamp, &ptpClock->PdelayReqHeader, dst, + rtOpts, ptpClock, sequenceId); +} + +static void +handlePdelayRespFollowUp(const MsgHeader *header, ssize_t length, + Boolean isFromSelf, const RunTimeOpts *rtOpts, + PtpClock *ptpClock) +{ + + if (ptpClock->portDS.delayMechanism == P2P) { + TimeInternal responseOriginTimestamp; + TimeInternal correctionField; + + DBG("PdelayRespfollowup message received : \n"); + + if(length < PDELAY_RESP_FOLLOW_UP_LENGTH) { + DBG("Error: PdelayRespfollowup message too short\n"); + ptpClock->counters.messageFormatErrors++; + return; + } + + switch(ptpClock->portDS.portState) { + case PTP_INITIALIZING: + case PTP_FAULTY: + case PTP_DISABLED: + case PTP_UNCALIBRATED: + DBGV("HandlePdelayResp : disregard\n"); + ptpClock->counters.discardedMessages++; + return; + + case PTP_SLAVE: + case PTP_MASTER: + if (isFromSelf) { + DBGV("HandlePdelayRespFollowUp : Ignore message from self \n"); + return; + } + if (( ((UInteger16)(header->sequenceId + 1)) == + ptpClock->sentPdelayReqSequenceId) && (header->sequenceId == ptpClock->recvPdelayRespSequenceId)) { + msgUnpackPdelayRespFollowUp( + ptpClock->msgIbuf, + &ptpClock->msgTmp.prespfollow); + ptpClock->counters.pdelayRespFollowUpMessagesReceived++; + toInternalTime( + &responseOriginTimestamp, + &ptpClock->msgTmp.prespfollow.responseOriginTimestamp); + ptpClock->pdelay_resp_send_time.seconds = + responseOriginTimestamp.seconds; + ptpClock->pdelay_resp_send_time.nanoseconds = + responseOriginTimestamp.nanoseconds; + integer64_to_internalTime( + ptpClock->msgTmpHeader.correctionField, + &correctionField); + addTime(&correctionField,&correctionField, + &ptpClock->lastPdelayRespCorrectionField); + updatePeerDelay (&ptpClock->mpd_filt, + rtOpts, ptpClock, + &correctionField,TRUE); + +/* pdelay interval handling begin */ + if (rtOpts->ignore_delayreq_interval_master == 0) { + DBGV("current delay_req: %d new delay req: %d \n", + ptpClock->portDS.logMinPdelayReqInterval, + header->logMessageInterval); + if (ptpClock->portDS.logMinPdelayReqInterval != header->logMessageInterval) { + + if(header->logMessageInterval == UNICAST_MESSAGEINTERVAL && + rtOpts->autoDelayReqInterval) { + + if(rtOpts->unicastNegotiation && ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].granted) { + if(ptpClock->delayRespWaiting) { + NOTICE("Received Peer Delay Interval %d from peer\n", + ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].logInterval); + } + ptpClock->portDS.logMinPdelayReqInterval = ptpClock->peerGrants.grantData[PDELAY_RESP_INDEXED].logInterval; + } else { + + if(ptpClock->delayRespWaiting) { + NOTICE("Received Peer Delay Interval %d from peer (unicast-unknown) - overriding with %d\n", + header->logMessageInterval, rtOpts->logMinPdelayReqInterval); + } + ptpClock->portDS.logMinPdelayReqInterval = rtOpts->logMinPdelayReqInterval; + + } + } else { + /* Accept new DelayReq value from the Master */ + + NOTICE("Received new Peer Delay Request interval %d from Master (was: %d)\n", + header->logMessageInterval, ptpClock->portDS.logMinPdelayReqInterval ); + + // collect new value indicated by the Master + ptpClock->portDS.logMinPdelayReqInterval = header->logMessageInterval; + } + } else { + + if (ptpClock->portDS.logMinPdelayReqInterval != rtOpts->logMinPdelayReqInterval) { + INFO("New Peer Delay Request interval applied: %d (was: %d)\n", + rtOpts->logMinPdelayReqInterval, ptpClock->portDS.logMinPdelayReqInterval); + } + ptpClock->portDS.logMinPdelayReqInterval = rtOpts->logMinPdelayReqInterval; + } + +/* pdelay interval handling end */ + } + ptpClock->delayRespWaiting = FALSE; + timerStart(&ptpClock->timers[DELAY_RECEIPT_TIMER], max( + (ptpClock->portDS.announceReceiptTimeout) * (pow(2,ptpClock->portDS.logAnnounceInterval)), + MISSED_MESSAGES_MAX * (pow(2,ptpClock->portDS.logMinPdelayReqInterval)))); + + break; + } else { + DBG("PdelayRespFollowup: sequence mismatch - Received: %d " + "PdelayReq sent: %d, PdelayResp received: %d\n", + header->sequenceId, ptpClock->sentPdelayReqSequenceId, + ptpClock->recvPdelayRespSequenceId); + ptpClock->counters.discardedMessages++; + ptpClock->counters.sequenceMismatchErrors++; + break; + } + default: + DBGV("Disregard PdelayRespFollowUp message \n"); + ptpClock->counters.discardedMessages++; + } + } else if (ptpClock->portDS.delayMechanism == E2E) { /* (End to End mode..) */ + DBG("Peer Delay messages are disregarded in End to End " + "mode \n"); + ptpClock->counters.discardedMessages++; + ptpClock->counters.delayMechanismMismatchErrors++; + /* no delay mechanism */ + } else { + DBG("PdelayRespFollowUp ignored - we are in DELAY_DISABLED mode"); + ptpClock->counters.discardedMessages++; + } +} + +/* Only accept the management / signaling message if it satisfies 15.3.1 Table 36 */ +/* Also 13.12.1 table 32 */ +Boolean +acceptPortIdentity(PortIdentity thisPort, PortIdentity targetPort) +{ + ClockIdentity allOnesClkIdentity; + UInteger16 allOnesPortNumber = 0xFFFF; + memset(allOnesClkIdentity, 0xFF, sizeof(allOnesClkIdentity)); + + /* equal port IDs: equal clock ID and equal port ID */ + if(!memcmp(targetPort.clockIdentity, thisPort.clockIdentity, CLOCK_IDENTITY_LENGTH) && + (targetPort.portNumber == thisPort.portNumber)) { + return TRUE; + } + + /* equal clockIDs and target port number is wildcard */ + if(!memcmp(targetPort.clockIdentity, thisPort.clockIdentity, CLOCK_IDENTITY_LENGTH) && + (targetPort.portNumber == allOnesPortNumber)) { + return TRUE; + } + + /* target clock ID is wildcard, port number matches */ + if(!memcmp(targetPort.clockIdentity, allOnesClkIdentity, CLOCK_IDENTITY_LENGTH) && + (targetPort.portNumber == thisPort.portNumber)) { + return TRUE; + } + + /* target port and clock IDs are both wildcard */ + if(!memcmp(targetPort.clockIdentity, allOnesClkIdentity, CLOCK_IDENTITY_LENGTH) && + (targetPort.portNumber == allOnesPortNumber)) { + return TRUE; + } + + return FALSE; +} + +/* This code does not get built in the slave-only build */ +#ifndef PTPD_SLAVE_ONLY + +/* send Announce to all destinations */ +static void +issueAnnounce(const RunTimeOpts *rtOpts,PtpClock *ptpClock) +{ + Integer32 dst = 0; + int i = 0; + UnicastGrantData *grant = NULL; + Boolean okToSend = TRUE; + + /* send Announce to Ethernet or multicast */ + if(rtOpts->transport == IEEE_802_3 || (rtOpts->ipMode != IPMODE_UNICAST)) { + issueAnnounceSingle(dst, &ptpClock->sentAnnounceSequenceId, rtOpts, ptpClock); + /* send Announce to unicast destination(s) */ + } else { + /* send to granted only */ + if(rtOpts->unicastNegotiation) { + for(i = 0; i < UNICAST_MAX_DESTINATIONS; i++) { + grant = &(ptpClock->unicastGrants[i].grantData[ANNOUNCE_INDEXED]); + okToSend = TRUE; + if(grant->logInterval > ptpClock->portDS.logAnnounceInterval ) { + grant->intervalCounter %= (UInteger32)(pow(2,grant->logInterval - ptpClock->portDS.logAnnounceInterval)); + if(grant->intervalCounter != 0) { + okToSend = FALSE; + } + grant->intervalCounter++; + } + if(grant->granted) { + if(okToSend) { + issueAnnounceSingle(ptpClock->unicastGrants[i].transportAddress, + &grant->sentSeqId,rtOpts, ptpClock); + } + } + } + /* send to fixed unicast destinations */ + } else { + for(i = 0; i < ptpClock->unicastDestinationCount; i++) { + issueAnnounceSingle(ptpClock->unicastDestinations[i].transportAddress, + &(ptpClock->unicastGrants[i].grantData[ANNOUNCE_INDEXED].sentSeqId), + rtOpts, ptpClock); + } + } + } + +} + +/* send single announce to a single destination */ +static void +issueAnnounceSingle(Integer32 dst, UInteger16 *sequenceId, const RunTimeOpts *rtOpts,PtpClock *ptpClock) +{ + + Timestamp originTimestamp; + TimeInternal internalTime; + + getTime(&internalTime); + fromInternalTime(&internalTime,&originTimestamp); + + msgPackAnnounce(ptpClock->msgObuf, *sequenceId, &originTimestamp, ptpClock); + + if (!netSendGeneral(ptpClock->msgObuf,ANNOUNCE_LENGTH, + &ptpClock->netPath, rtOpts, dst)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("Announce message can't be sent -> FAULTY state \n"); + } else { + DBGV("Announce MSG sent ! \n"); + (*sequenceId)++; + ptpClock->counters.announceMessagesSent++; + } +} + +/* send Sync to all destinations */ +static void +issueSync(const RunTimeOpts *rtOpts,PtpClock *ptpClock) +{ + Integer32 dst = 0; + int i = 0; + UnicastGrantData *grant = NULL; + Boolean okToSend = TRUE; + + /* send Sync to Ethernet or multicast */ + if(rtOpts->transport == IEEE_802_3 || (rtOpts->ipMode != IPMODE_UNICAST)) { + (void)issueSyncSingle(dst, &ptpClock->sentSyncSequenceId, rtOpts, ptpClock); + + /* send Sync to unicast destination(s) */ + } else { + for(i = 0; i < UNICAST_MAX_DESTINATIONS; i++) { + ptpClock->syncDestIndex[i].transportAddress = 0; + clearTime(&ptpClock->unicastGrants[i].lastSyncTimestamp); + clearTime(&ptpClock->unicastDestinations[i].lastSyncTimestamp); + } + /* send to granted only */ + if(rtOpts->unicastNegotiation) { + for(i = 0; i < UNICAST_MAX_DESTINATIONS; i++) { + grant = &(ptpClock->unicastGrants[i].grantData[SYNC_INDEXED]); + okToSend = TRUE; + /* handle different intervals */ + if(grant->logInterval > ptpClock->portDS.logSyncInterval ) { + grant->intervalCounter %= (UInteger32)(pow(2,grant->logInterval - ptpClock->portDS.logSyncInterval)); + if(grant->intervalCounter != 0) { + okToSend = FALSE; + } + DBG("mixed interval to %d counter: %d\n", grant->parent->transportAddress,grant->intervalCounter); + grant->intervalCounter++; + } + + if(grant->granted) { + if(okToSend) { + ptpClock->unicastGrants[i].lastSyncTimestamp = + issueSyncSingle(ptpClock->unicastGrants[i].transportAddress, + &grant->sentSeqId,rtOpts, ptpClock); + } + } + } + /* send to fixed unicast destinations */ + } else { + for(i = 0; i < ptpClock->unicastDestinationCount; i++) { + ptpClock->unicastDestinations[i].lastSyncTimestamp = + issueSyncSingle(ptpClock->unicastDestinations[i].transportAddress, + &(ptpClock->unicastGrants[i].grantData[SYNC_INDEXED].sentSeqId), + rtOpts, ptpClock); + } + } + } + +} + +/*Pack and send a single Sync message, return the embedded timestamp*/ +static TimeInternal +issueSyncSingle(Integer32 dst, UInteger16 *sequenceId, const RunTimeOpts *rtOpts,PtpClock *ptpClock) +{ + Timestamp originTimestamp; + TimeInternal internalTime, now; + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + // Read T0 and T0vlt + Score_PtpBusGetGlobalTime(); + +#else + getTime(&internalTime); + + + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + +#endif + // ##### SCORE MODIFICATION END ##### + + /* + * LEAPNOTE01# + * This is done here rather than in netSendEvent because we must also + * prevent sequence IDs from incrementing so that there's no discontinuity. + * Ideally netSendEvent should be incrementing the sequence numbers, + * then this could be centrally blocked, but then again this makes + * netSendEvent less generic - on the other end in 2.4 this will be + * a glue function for libcck transports, so this will be fine. + * In other words, ptpClock->netSendEvent() will be a function pointer. + * What was I talking about? Yes, right, sequence numbers can be + * incremented in netSendEvent. + */ + + if(ptpClock->leapSecondInProgress) { + DBG("Leap second in progress - will not send SYNC\n"); + clearTime(&internalTime); + return internalTime; + } + + fromInternalTime(&internalTime,&originTimestamp); + + now = internalTime; + + msgPackSync(ptpClock->msgObuf,*sequenceId,&originTimestamp,ptpClock); + +#if PTPD_SCORE_DEBUG + printf("\n\n[DEBUG] aptpd2: Sending packet-Sync now...\n"); + for (int i = 0; i < SYNC_LENGTH; i++) + { + printf("(%d:%02X) ", i, (unsigned char)(ptpClock->msgObuf[i])); + if(0 == (i%10)) { + printf("\n"); + } + } +#endif + if (!netSendEvent(ptpClock->msgObuf,SYNC_LENGTH,&ptpClock->netPath, + rtOpts, dst, &internalTime)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("Sync message can't be sent -> FAULTY state \n"); + } else { + + DBGV("Sync MSG sent ! \n"); + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + // Read T4vlt and Compute PreciseOriginTime as soon as Sync message is sent + Score_PtpProviderComputeOriginTime(); + +#endif + +#ifdef SO_TIMESTAMPING + +#ifdef PTPD_PCAP + if((ptpClock->netPath.pcapEvent == NULL) && !ptpClock->netPath.txTimestampFailure) { +#else + if(!ptpClock->netPath.txTimestampFailure) { +#endif /* PTPD_PCAP */ + if(internalTime.seconds && internalTime.nanoseconds) { + + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + processSyncFromSelf(&internalTime, rtOpts, ptpClock, dst, *sequenceId); + } + } +#endif + +#if defined(__QNXNTO__) && defined(PTPD_EXPERIMENTAL) + if(internalTime.seconds && internalTime.nanoseconds) { + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + processSyncFromSelf(&internalTime, rtOpts, ptpClock, dst, *sequenceId); + } +#endif + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + /* Processing Sync from same master */ + processSyncFromSelf(&internalTime, rtOpts, ptpClock, dst, *sequenceId); +#endif + // ##### SCORE MODIFICATION END ##### + + ptpClock->lastSyncDst = dst; + + if(!internalTime.seconds && !internalTime.nanoseconds) { + internalTime = now; + } + + /* index the Sync destination */ + indexSync(&internalTime, *sequenceId, dst, ptpClock->syncDestIndex); + + (*sequenceId)++; + ptpClock->counters.syncMessagesSent++; + + } + + return internalTime; +} + + + +/*Pack and send on general multicast ip adress a FollowUp message*/ +static void +issueFollowup(const TimeInternal *tint,const RunTimeOpts *rtOpts,PtpClock *ptpClock, Integer32 dst, UInteger16 sequenceId) +{ + Timestamp preciseOriginTimestamp; + + // ##### SCORE MODIFICATION BEGIN ##### +#if SCORE_EXTENSIONS + // Read T4vlt and Compute PreciseOriginTime + Score_PtpWriteOriginTimeToBuffer( (uint8_t*)(&preciseOriginTimestamp), sizeof(Timestamp)); +#else + + fromInternalTime(tint,&preciseOriginTimestamp); + +#endif + // ##### SCORE MODIFICATION END ##### + + /* Pack follow-up Message-Header + preceiseOriginTime */ + msgPackFollowUp(ptpClock->msgObuf,&preciseOriginTimestamp,ptpClock,sequenceId); + +#if PTPD_SCORE_DEBUG + printf("[DEBUG] aptpd2: Sending packet-FUP now...\n"); + for (int i = 0; i < ptpConfig.configGlobals.followupLength; i++) + { + printf("(%d:%02X) ", i, (unsigned char)(ptpClock->msgObuf[i])); + if(0 == (i%10)) { + printf("\n"); + } + } +#endif + + + if (!netSendGeneral(ptpClock->msgObuf,ptpConfig.configGlobals.followupLength, + &ptpClock->netPath, rtOpts, dst)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("FollowUp message can't be sent -> FAULTY state \n"); + } else { + DBGV("FollowUp MSG sent ! \n"); + ptpClock->counters.followUpMessagesSent++; + } +} + +#endif /* PTPD_SLAVE_ONLY */ + +/*Pack and send on event multicast ip adress a DelayReq message*/ +static void +issueDelayReq(const RunTimeOpts *rtOpts,PtpClock *ptpClock) +{ + Timestamp originTimestamp; + TimeInternal internalTime; +#if 0 /* PCAP ONLY */ + MsgHeader ourDelayReq; +#endif + /* see LEAPNOTE01# in this file */ + if(ptpClock->leapSecondInProgress) { + DBG("Leap second in progress - will not send DELAY_REQ\n"); + return; + } + + DBG("==> Issue DelayReq (%d)\n", ptpClock->sentDelayReqSequenceId ); + + /* + * call GTOD. This time is later replaced in handleDelayReq, + * to get the actual send timestamp from the OS + */ + getTime(&internalTime); + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + fromInternalTime(&internalTime,&originTimestamp); + + // uses current sentDelayReqSequenceId + msgPackDelayReq(ptpClock->msgObuf,&originTimestamp,ptpClock); + + Integer32 dst = 0; + + /* in hybrid mode or unicast mode, send delayReq to current master */ + if (rtOpts->ipMode == IPMODE_HYBRID || rtOpts->ipMode == IPMODE_UNICAST) { + if(ptpClock->bestMaster) { + dst = ptpClock->bestMaster->sourceAddr; + } + } + + if (!netSendEvent(ptpClock->msgObuf,DELAY_REQ_LENGTH, + &ptpClock->netPath, rtOpts, dst, &internalTime)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("delayReq message can't be sent -> FAULTY state \n"); + } else { + DBGV("DelayReq MSG sent ! \n"); + +#ifdef SO_TIMESTAMPING + +#ifdef PTPD_PCAP + if((ptpClock->netPath.pcapEvent == NULL) && !ptpClock->netPath.txTimestampFailure) { +#else + if(!ptpClock->netPath.txTimestampFailure) { +#endif /* PTPD_PCAP */ + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + + processDelayReqFromSelf(&internalTime, rtOpts, ptpClock); + } +#endif + +#if defined(__QNXNTO__) && defined(PTPD_EXPERIMENTAL) + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + + processDelayReqFromSelf(&internalTime, rtOpts, ptpClock); +#endif + + ptpClock->sentDelayReqSequenceId++; + ptpClock->counters.delayReqMessagesSent++; + + /* From now on, we will only accept delayreq and + * delayresp of (sentDelayReqSequenceId - 1) */ + + /* Explicitly re-arm timer for sending the next delayReq + * 9.5.11.2: arm the timer with a uniform range from 0 to 2 x interval + * this is only ever used here, so removed the timerStart_random function + */ + + timerStart(&ptpClock->timers[DELAYREQ_INTERVAL_TIMER], + pow(2,ptpClock->portDS.logMinDelayReqInterval) * getRand() * 2.0); +#if 0 /* PCAP ONLY */ + msgUnpackHeader(ptpClock->msgObuf, &ourDelayReq); + handleDelayReq(&ourDelayReq, DELAY_REQ_LENGTH, &internalTime, + TRUE, rtOpts, ptpClock); +#endif + } +} + +/*Pack and send on event multicast ip adress a PdelayReq message*/ +static void +issuePdelayReq(const RunTimeOpts *rtOpts,PtpClock *ptpClock) +{ + Integer32 dst = 0; + Timestamp originTimestamp; + TimeInternal internalTime; + + /* see LEAPNOTE01# in this file */ + if(ptpClock->leapSecondInProgress) { + DBG("Leap secon in progress - will not send PDELAY_REQ\n"); + return; + } + + getTime(&internalTime); + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + fromInternalTime(&internalTime,&originTimestamp); + + if(rtOpts->ipMode == IPMODE_UNICAST && ptpClock->unicastPeerDestination.transportAddress) { + dst = ptpClock->unicastPeerDestination.transportAddress; + } + + msgPackPdelayReq(ptpClock->msgObuf,&originTimestamp,ptpClock); + if (!netSendPeerEvent(ptpClock->msgObuf,PDELAY_REQ_LENGTH, + &ptpClock->netPath, rtOpts, dst, &internalTime)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("PdelayReq message can't be sent -> FAULTY state \n"); + } else { + DBGV("PdelayReq MSG sent ! \n"); + +#ifdef SO_TIMESTAMPING + +#ifdef PTPD_PCAP + if((ptpClock->netPath.pcapEvent == NULL) && !ptpClock->netPath.txTimestampFailure) { +#else + if(!ptpClock->netPath.txTimestampFailure) { +#endif /* PTPD_PCAP */ + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + processPdelayReqFromSelf(&internalTime, rtOpts, ptpClock); + } +#endif + + ptpClock->sentPdelayReqSequenceId++; + ptpClock->counters.pdelayReqMessagesSent++; + } +} + +/*Pack and send on event multicast ip adress a PdelayResp message*/ +static void +issuePdelayResp(const TimeInternal *tint,MsgHeader *header, Integer32 sourceAddress, const RunTimeOpts *rtOpts, + PtpClock *ptpClock) +{ + + Timestamp requestReceiptTimestamp; + TimeInternal internalTime; + + Integer32 dst = 0; + + /* see LEAPNOTE01# in this file */ + if(ptpClock->leapSecondInProgress) { + DBG("Leap second in progress - will not send PDELAY_RESP\n"); + return; + } + + /* if request was unicast and we're running unicast, reply to source */ + if ( (rtOpts->ipMode != IPMODE_MULTICAST) && + (header->flagField0 & PTP_UNICAST) == PTP_UNICAST) { + dst = sourceAddress; + } + + fromInternalTime(tint,&requestReceiptTimestamp); + msgPackPdelayResp(ptpClock->msgObuf,header, + &requestReceiptTimestamp,ptpClock); + + if (!netSendPeerEvent(ptpClock->msgObuf,PDELAY_RESP_LENGTH, + &ptpClock->netPath, rtOpts, dst, &internalTime)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("PdelayResp message can't be sent -> FAULTY state \n"); + } else { + DBGV("PdelayResp MSG sent ! \n"); + +#ifdef SO_TIMESTAMPING + +#ifdef PTPD_PCAP + if((ptpClock->netPath.pcapEvent == NULL) && !ptpClock->netPath.txTimestampFailure) { +#else + if(!ptpClock->netPath.txTimestampFailure) { +#endif /* PTPD_PCAP */ + if (respectUtcOffset(rtOpts, ptpClock) == TRUE) { + internalTime.seconds += ptpClock->timePropertiesDS.currentUtcOffset; + } + processPdelayRespFromSelf(&internalTime, rtOpts, ptpClock, dst, header->sequenceId); + } +#endif + + ptpClock->counters.pdelayRespMessagesSent++; + ptpClock->lastPdelayRespDst = dst; + } +} + + +/*Pack and send a DelayResp message on event socket*/ +static void +issueDelayResp(const TimeInternal *tint,MsgHeader *header,Integer32 sourceAddress, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + Timestamp requestReceiptTimestamp; + Integer32 dst; + + fromInternalTime(tint,&requestReceiptTimestamp); + msgPackDelayResp(ptpClock->msgObuf,header,&requestReceiptTimestamp, + ptpClock); + + /* if request was unicast and we're running unicast, reply to source */ + if ( (rtOpts->ipMode != IPMODE_MULTICAST) && + (header->flagField0 & PTP_UNICAST) == PTP_UNICAST) { + dst = sourceAddress; + } else { + dst = 0; + } + + if (!netSendGeneral(ptpClock->msgObuf, DELAY_RESP_LENGTH, + &ptpClock->netPath, rtOpts, dst)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("delayResp message can't be sent -> FAULTY state \n"); + } else { + DBGV("PdelayResp MSG sent ! \n"); + ptpClock->counters.delayRespMessagesSent++; + } +} + +static void +issuePdelayRespFollowUp(const TimeInternal *tint, MsgHeader *header, Integer32 dst, + const RunTimeOpts *rtOpts, PtpClock *ptpClock, const UInteger16 sequenceId) +{ + Timestamp responseOriginTimestamp; + fromInternalTime(tint,&responseOriginTimestamp); + + msgPackPdelayRespFollowUp(ptpClock->msgObuf,header, + &responseOriginTimestamp,ptpClock, sequenceId); + if (!netSendPeerGeneral(ptpClock->msgObuf, + PDELAY_RESP_FOLLOW_UP_LENGTH, + &ptpClock->netPath, rtOpts, dst)) { + toState(PTP_FAULTY,rtOpts,ptpClock); + ptpClock->counters.messageSendErrors++; + DBGV("PdelayRespFollowUp message can't be sent -> FAULTY state \n"); + } else { + DBGV("PdelayRespFollowUp MSG sent ! \n"); + ptpClock->counters.pdelayRespFollowUpMessagesSent++; + } +} + +void +addForeign(Octet *buf,MsgHeader *header,PtpClock *ptpClock, UInteger8 localPreference, UInteger32 sourceAddr) +{ + int i,j; + Boolean found = FALSE; + + DBGV("addForeign localPref: %d\n", localPreference); + + j = ptpClock->foreign_record_best; + + /*Check if Foreign master is already known*/ + for (i=0;inumber_foreign_records;i++) { + if (!memcmp(header->sourcePortIdentity.clockIdentity, + ptpClock->foreign[j].foreignMasterPortIdentity.clockIdentity, + CLOCK_IDENTITY_LENGTH) && + (header->sourcePortIdentity.portNumber == + ptpClock->foreign[j].foreignMasterPortIdentity.portNumber)) + { + /*Foreign Master is already in Foreignmaster data set*/ + ptpClock->foreign[j].foreignMasterAnnounceMessages++; + found = TRUE; + DBGV("addForeign : AnnounceMessage incremented \n"); + msgUnpackHeader(buf,&ptpClock->foreign[j].header); + msgUnpackAnnounce(buf,&ptpClock->foreign[j].announce); + ptpClock->foreign[j].disqualified = FALSE; + ptpClock->foreign[j].localPreference = localPreference; + break; + } + + j = (j+1)%ptpClock->number_foreign_records; + } + + /*New Foreign Master*/ + if (!found) { + if (ptpClock->number_foreign_records < + ptpClock->max_foreign_records) { + ptpClock->number_foreign_records++; + } + + /* Preserve best master record from overwriting (sf FR #22) - use next slot */ + if (ptpClock->foreign_record_i == ptpClock->foreign_record_best) { + ptpClock->foreign_record_i++; + ptpClock->foreign_record_i %= ptpClock->number_foreign_records; + } + + j = ptpClock->foreign_record_i; + + /*Copy new foreign master data set from Announce message*/ + copyClockIdentity(ptpClock->foreign[j].foreignMasterPortIdentity.clockIdentity, + header->sourcePortIdentity.clockIdentity); + ptpClock->foreign[j].foreignMasterPortIdentity.portNumber = + header->sourcePortIdentity.portNumber; + ptpClock->foreign[j].foreignMasterAnnounceMessages = 0; + ptpClock->foreign[j].localPreference = localPreference; + ptpClock->foreign[j].sourceAddr = sourceAddr; + ptpClock->foreign[j].disqualified = FALSE; + /* + * header and announce field of each Foreign Master are + * usefull to run Best Master Clock Algorithm + */ + msgUnpackHeader(buf,&ptpClock->foreign[j].header); + msgUnpackAnnounce(buf,&ptpClock->foreign[j].announce); + DBGV("New foreign Master added \n"); + + ptpClock->foreign_record_i = + (ptpClock->foreign_record_i+1) % + ptpClock->max_foreign_records; + } +} + +/* Update dataset fields which are safe to change without going into INITIALIZING */ +void +updateDatasets(PtpClock* ptpClock, const RunTimeOpts* rtOpts) +{ + + if(rtOpts->unicastNegotiation) { + updateUnicastGrantTable(ptpClock->unicastGrants, + ptpClock->unicastDestinationCount, rtOpts); + if(rtOpts->unicastPeerDestinationSet) { + updateUnicastGrantTable(&ptpClock->peerGrants, + 1, rtOpts); + + } + } + + memset(ptpClock->userDescription, 0, sizeof(ptpClock->userDescription)); + memcpy(ptpClock->userDescription, rtOpts->portDescription, strlen(rtOpts->portDescription)); + + switch(ptpClock->portDS.portState) { + + /* We are master so update both the port and the parent dataset */ + case PTP_MASTER: + + if(rtOpts->dot1AS) { + ptpClock->portDS.transportSpecific = TSP_ETHERNET_AVB; + } else { + ptpClock->portDS.transportSpecific = TSP_DEFAULT; + } + + ptpClock->defaultDS.numberPorts = NUMBER_PORTS; + ptpClock->portDS.portIdentity.portNumber = rtOpts->portNumber; + + ptpClock->portDS.delayMechanism = rtOpts->delayMechanism; + ptpClock->portDS.versionNumber = VERSION_PTP; + + ptpClock->defaultDS.clockQuality.clockAccuracy = + rtOpts->clockQuality.clockAccuracy; + ptpClock->defaultDS.clockQuality.clockClass = rtOpts->clockQuality.clockClass; + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance = + rtOpts->clockQuality.offsetScaledLogVariance; + ptpClock->defaultDS.priority1 = rtOpts->priority1; + ptpClock->defaultDS.priority2 = rtOpts->priority2; + + ptpClock->parentDS.grandmasterClockQuality.clockAccuracy = + ptpClock->defaultDS.clockQuality.clockAccuracy; + ptpClock->parentDS.grandmasterClockQuality.clockClass = + ptpClock->defaultDS.clockQuality.clockClass; + ptpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance = + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance; + ptpClock->defaultDS.clockQuality.clockAccuracy = + ptpClock->parentDS.grandmasterPriority1 = ptpClock->defaultDS.priority1; + ptpClock->parentDS.grandmasterPriority2 = ptpClock->defaultDS.priority2; + ptpClock->timePropertiesDS.currentUtcOffsetValid = rtOpts->timeProperties.currentUtcOffsetValid; + ptpClock->timePropertiesDS.currentUtcOffset = rtOpts->timeProperties.currentUtcOffset; + ptpClock->timePropertiesDS.timeTraceable = rtOpts->timeProperties.timeTraceable; + ptpClock->timePropertiesDS.frequencyTraceable = rtOpts->timeProperties.frequencyTraceable; + ptpClock->timePropertiesDS.ptpTimescale = rtOpts->timeProperties.ptpTimescale; + ptpClock->timePropertiesDS.timeSource = rtOpts->timeProperties.timeSource; + ptpClock->portDS.logAnnounceInterval = rtOpts->logAnnounceInterval; + ptpClock->portDS.announceReceiptTimeout = rtOpts->announceReceiptTimeout; + ptpClock->portDS.logSyncInterval = rtOpts->logSyncInterval; + ptpClock->portDS.logMinPdelayReqInterval = rtOpts->logMinPdelayReqInterval; + ptpClock->portDS.logMinDelayReqInterval = rtOpts->initial_delayreq; + break; + /* + * we are not master so update the port dataset only - parent will be updated + * by m1() if we go master - basically update the fields affecting BMC only + */ + case PTP_SLAVE: + if(ptpClock->portDS.logMinDelayReqInterval == UNICAST_MESSAGEINTERVAL && + rtOpts->autoDelayReqInterval) { + NOTICE("Running at %d Delay Interval (unicast) - overriding with %d\n", + ptpClock->portDS.logMinDelayReqInterval, rtOpts->logMinDelayReqInterval); + ptpClock->portDS.logMinDelayReqInterval = rtOpts->logMinDelayReqInterval; + } + case PTP_PASSIVE: + ptpClock->defaultDS.numberPorts = NUMBER_PORTS; + ptpClock->portDS.portIdentity.portNumber = rtOpts->portNumber; + + if(rtOpts->dot1AS) { + ptpClock->portDS.transportSpecific = TSP_ETHERNET_AVB; + } else { + ptpClock->portDS.transportSpecific = TSP_DEFAULT; + } + + ptpClock->portDS.delayMechanism = rtOpts->delayMechanism; + ptpClock->portDS.versionNumber = VERSION_PTP; + ptpClock->defaultDS.clockQuality.clockAccuracy = + rtOpts->clockQuality.clockAccuracy; + ptpClock->defaultDS.clockQuality.clockClass = rtOpts->clockQuality.clockClass; + ptpClock->defaultDS.clockQuality.offsetScaledLogVariance = + rtOpts->clockQuality.offsetScaledLogVariance; + ptpClock->defaultDS.priority1 = rtOpts->priority1; + ptpClock->defaultDS.priority2 = rtOpts->priority2; + break; + /* in DISABLED state we still listen for management messages */ + case PTP_DISABLED: + ptpClock->portDS.versionNumber = VERSION_PTP; + default: + /* In all other states the datasets will be updated when going into an operational state */ + break; + } + +} + +void +clearCounters(PtpClock * ptpClock) +{ + /* TODO: print port info */ + DBG("Port counters cleared\n"); + memset(&ptpClock->counters, 0, sizeof(ptpClock->counters)); +} + +Boolean +respectUtcOffset(const RunTimeOpts * rtOpts, PtpClock * ptpClock) { + if (ptpClock->timePropertiesDS.currentUtcOffsetValid || rtOpts->alwaysRespectUtcOffset) { + return TRUE; + } + return FALSE; +} + diff --git a/src/ptpd/src/ptp_datatypes.h b/src/ptpd/src/ptp_datatypes.h new file mode 100644 index 0000000..3cf50c8 --- /dev/null +++ b/src/ptpd/src/ptp_datatypes.h @@ -0,0 +1,604 @@ +#ifndef PTP_DATATYPES_H_ +#define PTP_DATATYPES_H_ + +#include "ptp_primitives.h" +#include "inc/score_ptp_config.h" +/*Struct defined in spec*/ + + +/** +*\file +* \brief Main structures used in ptpdv2 +* +* This header file defines structures defined by the spec, +* main program data structure, and all messages structures + */ + +/** +* \brief Time structure to handle timestamps + */ +typedef struct { + Integer32 seconds; + Integer32 nanoseconds; +} TimeInternal; + +/** +* \brief The TimeInterval type represents time intervals + */ +typedef struct { + /* see src/def/README for a note on this X-macro */ + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/timeInterval.def" +} TimeInterval; + +/** +* \brief The Timestamp type represents a positive time with respect to the epoch + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/timestamp.def" +} Timestamp; + +/** +* \brief The ClockIdentity type identifies a clock + */ +typedef Octet ClockIdentity[CLOCK_IDENTITY_LENGTH]; + +/** +* \brief The PortIdentity identifies a PTP port. + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/portIdentity.def" +} PortIdentity; + +/** +* \brief The PortAdress type represents the protocol address of a PTP port + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/portAddress.def" +} PortAddress; + +/** +* \brief The ClockQuality represents the quality of a clock + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/clockQuality.def" +} ClockQuality; + +/** +* \brief The TimePropertiesDS type represent time source and traceability properties of a clock + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/timePropertiesDS.def" +} TimePropertiesDS; + +/** +* \brief The TLV type represents TLV extension fields + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/tlv.def" +} TLV; + +/** +* \brief The PTPText data type is used to represent textual material in PTP messages + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/ptpText.def" +} PTPText; + +/** +* \brief The FaultRecord type is used to construct fault logs + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/faultRecord.def" +} FaultRecord; + +/** +* \brief The PhysicalAddress type is used to represent a physical address + */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/derivedData/physicalAddress.def" +} PhysicalAddress; + + +/** +* \brief The common header for all PTP messages (Table 18 of the spec) + */ +// ##### SCORE MODIFICATION BEGIN ##### +/* Message header */ +typedef struct MsgHeaderTag { + #define OPERATE( name, size, type ) type name; + #include "def/message/header.def" +} MsgHeader; +// ##### SCORE MODIFICATION END ##### + +/** +* \brief Announce message fields (Table 25 of the spec) + */ +/*Announce Message */ +typedef struct { + Timestamp originTimestamp; + Integer16 currentUtcOffset; + UInteger8 grandmasterPriority1; + ClockQuality grandmasterClockQuality; + UInteger8 grandmasterPriority2; + ClockIdentity grandmasterIdentity; + UInteger16 stepsRemoved; + Enumeration8 timeSource; +}MsgAnnounce; + + +/** +* \brief Sync message fields (Table 26 of the spec) + */ +/*Sync Message */ +typedef struct { + Timestamp originTimestamp; +}MsgSync; + +/** +* \brief DelayReq message fields (Table 26 of the spec) + */ +/*DelayReq Message */ +typedef struct { + Timestamp originTimestamp; +}MsgDelayReq; + +/** +* \brief DelayResp message fields (Table 30 of the spec) + */ +/*delayResp Message*/ +typedef struct { + Timestamp receiveTimestamp; + PortIdentity requestingPortIdentity; +}MsgDelayResp; + +/** +* \brief FollowUp message fields (Table 27 of the spec) + */ +/*Follow-up Message*/ +typedef struct { + Timestamp preciseOriginTimestamp; +}MsgFollowUp; + +/** +* \brief PdelayReq message fields (Table 29 of the spec) + */ +/*PdelayReq Message*/ +typedef struct { + Timestamp originTimestamp; +}MsgPdelayReq; + +/** +* \brief PdelayResp message fields (Table 30 of the spec) + */ +/*PdelayResp Message*/ +typedef struct { + Timestamp requestReceiptTimestamp; + PortIdentity requestingPortIdentity; +}MsgPdelayResp; + +/** +* \brief PdelayRespFollowUp message fields (Table 31 of the spec) + */ +/*PdelayRespFollowUp Message*/ +typedef struct { + Timestamp responseOriginTimestamp; + PortIdentity requestingPortIdentity; +} MsgPdelayRespFollowUp; + + +/** + * \brief Management TLV message fields + */ +/* Management TLV Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/managementTLV.def" + Octet* dataField; +} ManagementTLV; + +/** + * \brief Management TLV Clock Description fields (Table 41 of the spec) + */ +/* Management TLV Clock Description Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/clockDescription.def" +} MMClockDescription; + +/** + * \brief Management TLV User Description fields (Table 43 of the spec) + */ +/* Management TLV User Description Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/userDescription.def" +} MMUserDescription; + +/** + * \brief Management TLV Initialize fields (Table 44 of the spec) + */ +/* Management TLV Initialize Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/initialize.def" +} MMInitialize; + +/** + * \brief Management TLV Default Data Set fields (Table 50 of the spec) + */ +/* Management TLV Default Data Set Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/defaultDataSet.def" +} MMDefaultDataSet; + +/** + * \brief Management TLV Current Data Set fields (Table 55 of the spec) + */ +/* Management TLV Current Data Set Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/currentDataSet.def" +} MMCurrentDataSet; + +/** + * \brief Management TLV Parent Data Set fields (Table 56 of the spec) + */ +/* Management TLV Parent Data Set Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/parentDataSet.def" +} MMParentDataSet; + +/** + * \brief Management TLV Time Properties Data Set fields (Table 57 of the spec) + */ +/* Management TLV Time Properties Data Set Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/timePropertiesDataSet.def" +} MMTimePropertiesDataSet; + +/** + * \brief Management TLV Port Data Set fields (Table 61 of the spec) + */ +/* Management TLV Port Data Set Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/portDataSet.def" +} MMPortDataSet; + +/** + * \brief Management TLV Priority1 fields (Table 51 of the spec) + */ +/* Management TLV Priority1 Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/priority1.def" +} MMPriority1; + +/** + * \brief Management TLV Priority2 fields (Table 52 of the spec) + */ +/* Management TLV Priority2 Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/priority2.def" +} MMPriority2; + +/** + * \brief Management TLV Domain fields (Table 53 of the spec) + */ +/* Management TLV Domain Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/domain.def" +} MMDomain; + +/** + * \brief Management TLV Slave Only fields (Table 54 of the spec) + */ +/* Management TLV Slave Only Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/slaveOnly.def" +} MMSlaveOnly; + +/** + * \brief Management TLV Log Announce Interval fields (Table 62 of the spec) + */ +/* Management TLV Log Announce Interval Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/logAnnounceInterval.def" +} MMLogAnnounceInterval; + +/** + * \brief Management TLV Announce Receipt Timeout fields (Table 63 of the spec) + */ +/* Management TLV Announce Receipt Timeout Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/announceReceiptTimeout.def" +} MMAnnounceReceiptTimeout; + +/** + * \brief Management TLV Log Sync Interval fields (Table 64 of the spec) + */ +/* Management TLV Log Sync Interval Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/logSyncInterval.def" +} MMLogSyncInterval; + +/** + * \brief Management TLV Version Number fields (Table 67 of the spec) + */ +/* Management TLV Version Number Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/versionNumber.def" +} MMVersionNumber; + +/** + * \brief Management TLV Time fields (Table 48 of the spec) + */ +/* Management TLV Time Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/time.def" +} MMTime; + +/** + * \brief Management TLV Clock Accuracy fields (Table 49 of the spec) + */ +/* Management TLV Clock Accuracy Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/clockAccuracy.def" +} MMClockAccuracy; + +/** + * \brief Management TLV UTC Properties fields (Table 58 of the spec) + */ +/* Management TLV UTC Properties Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/utcProperties.def" +} MMUtcProperties; + +/** + * \brief Management TLV Traceability Properties fields (Table 59 of the spec) + */ +/* Management TLV Traceability Properties Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/traceabilityProperties.def" +} MMTraceabilityProperties; + +/** + * \brief Management TLV Timescale Properties fields (Table 59 of the spec) + */ +/* Management TLV Timescale Properties Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/timescaleProperties.def" +} MMTimescaleProperties; + +/** + * \brief Management TLV Unicast Negotiation Enable fields (Table 73 of the spec) + */ +/* Management TLV Traceability Properties Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/unicastNegotiationEnable.def" +} MMUnicastNegotiationEnable; + +/** + * \brief Management TLV Delay Mechanism fields (Table 65 of the spec) + */ +/* Management TLV Delay Mechanism Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/delayMechanism.def" +} MMDelayMechanism; + +/** + * \brief Management TLV Log Min Pdelay Req Interval fields (Table 66 of the spec) + */ +/* Management TLV Log Min Pdelay Req Interval Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/logMinPdelayReqInterval.def" +} MMLogMinPdelayReqInterval; + +/** + * \brief Management TLV Error Status fields (Table 71 of the spec) + */ +/* Management TLV Error Status Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/managementTLV/errorStatus.def" +} MMErrorStatus; + +/** +* \brief Management message fields (Table 37 of the spec) + */ +/*management Message*/ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/message/management.def" + ManagementTLV* tlv; +} MsgManagement; + +/** + * \brief Signaling TLV message fields (tab + */ +/* Signaling TLV Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/signalingTLV/signalingTLV.def" + Octet* valueField; +} SignalingTLV; + +/** + * \brief Signaling TLV Request Unicast Transmission fields (Table 73 of the spec) + */ +/* Signaling TLV Request Unicast Transmission Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/signalingTLV/requestUnicastTransmission.def" +} SMRequestUnicastTransmission; + +/** + * \brief Signaling TLV Grant Unicast Transmission fields (Table 74 of the spec) + */ +/* Signaling TLV Grant Unicast Transmission Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/signalingTLV/grantUnicastTransmission.def" +} SMGrantUnicastTransmission; + +/** + * \brief Signaling TLV Cancel Unicast Transmission fields (Table 75 of the spec) + */ +/* Signaling TLV Cancel Unicast Transmission Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/signalingTLV/cancelUnicastTransmission.def" +} SMCancelUnicastTransmission; + +/** + * \brief Signaling TLV Acknowledge Cancel Unicast Transmission fields (Table 76 of the spec) + */ +/* Signaling TLV Acknowledge Cancel Unicast Transmission Message */ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/signalingTLV/acknowledgeCancelUnicastTransmission.def" +} SMAcknowledgeCancelUnicastTransmission; + +/** +* \brief Signaling message fields (Table 33 of the spec) + */ +/*Signaling Message*/ +typedef struct { + #define OPERATE( name, size, type ) type name; + #include "def/message/signaling.def" + SignalingTLV* tlv; +} MsgSignaling; + + +/** +* \brief ForeignMasterRecord is used to manage foreign masters + */ +typedef struct +{ + PortIdentity foreignMasterPortIdentity; + UInteger16 foreignMasterAnnounceMessages; + + /* Supplementary data */ + MsgAnnounce announce; /* announce message -> all datasets */ + MsgHeader header; /* header -> some datasets */ + UInteger8 localPreference; /* local preference - only used by telecom profile */ + UInteger32 sourceAddr; /* source address */ + Boolean disqualified; /* if true, this one always loses */ +} ForeignMasterRecord; + +typedef struct { + + /*Static members*/ + Boolean twoStepFlag; + ClockIdentity clockIdentity; + UInteger16 numberPorts; + + /*Dynamic members*/ + ClockQuality clockQuality; + + /*Configurable members*/ + UInteger8 priority1; + UInteger8 priority2; + UInteger8 domainNumber; + Boolean slaveOnly; + +} DefaultDS; + +typedef struct { + + UInteger16 stepsRemoved; + TimeInternal offsetFromMaster; + TimeInternal meanPathDelay; + + /* PTPd additions */ + UInteger32 offsetFromMasterThreshold; /* maximum offset from master */ + +} CurrentDS; + +typedef struct { + /*Dynamic members*/ + PortIdentity parentPortIdentity; + Boolean parentStats; + UInteger16 observedParentOffsetScaledLogVariance; + Integer32 observedParentClockPhaseChangeRate; + ClockIdentity grandmasterIdentity; + ClockQuality grandmasterClockQuality; + UInteger8 grandmasterPriority1; + UInteger8 grandmasterPriority2; +} ParentDS; + +typedef struct { + + /*Static members*/ + PortIdentity portIdentity; + + /*Dynamic members*/ + Enumeration8 portState; + Integer8 logMinDelayReqInterval; + TimeInternal peerMeanPathDelay; + + /*Configurable members*/ + Integer8 logAnnounceInterval; + UInteger8 announceReceiptTimeout; + Integer8 logSyncInterval; + Enumeration8 delayMechanism; + Integer8 logMinPdelayReqInterval; + UInteger4 versionNumber; + + /* PTPd additions */ + Enumeration8 lastPortState; /* previous state */ + Nibble transportSpecific; /* TransportSpecific for 802.1AS */ + Integer16 lastMismatchedDomain; /* domain mismatch detection */ + +} PortDS; + +/* structure to hold basic PTP datasets when capturing an event */ +typedef struct { + DefaultDS defaultDS; + CurrentDS currentDS; + TimePropertiesDS timePropertiesDS; + PortDS portDS; + ParentDS parentDS; + ForeignMasterRecord bestMaster; + Integer32 ofmAlarmThreshold; +} PtpEventData; + + +// ##### SCORE MODIFICATION BEGIN ##### + +/** @brief The variable holding the length of the followup message. + * The length depends on the configuration parameter "MessageCompliance" + */ + +extern Score_PtpConfigType ptpConfig; + +// ##### SCORE MODIFICATION END ##### +#endif /*PTP_DATATYPES_H_*/ diff --git a/src/ptpd/src/ptp_primitives.h b/src/ptpd/src/ptp_primitives.h new file mode 100644 index 0000000..56ded20 --- /dev/null +++ b/src/ptpd/src/ptp_primitives.h @@ -0,0 +1,41 @@ +#ifndef PTP_PRIMITIVES_H_ +#define PTP_PRIMITIVES_H_ + + +typedef enum {FALSE=0, TRUE} Boolean; +typedef char Octet; +typedef int8_t Integer8; +typedef int16_t Integer16; +typedef int32_t Integer32; +typedef uint8_t UInteger8; +typedef uint16_t UInteger16; +typedef uint32_t UInteger32; +typedef uint16_t Enumeration16; +typedef unsigned char Enumeration8; +typedef unsigned char Enumeration4; +typedef unsigned char Enumeration4Upper; +typedef unsigned char Enumeration4Lower; +typedef unsigned char UInteger4; +typedef unsigned char UInteger4Upper; +typedef unsigned char UInteger4Lower; +typedef unsigned char Nibble; +typedef unsigned char NibbleUpper; +typedef unsigned char NibbleLower; + +/** +* \brief Implementation specific of UInteger48 type + */ +typedef struct { + uint32_t lsb; + uint16_t msb; +} UInteger48; + +/** +* \brief Implementation specific of Integer64 type + */ +typedef struct { + uint32_t lsb; + int32_t msb; +} Integer64; + +#endif /*PTP_PRIMITIVES_H_*/ diff --git a/src/ptpd/src/ptp_timers.c b/src/ptpd/src/ptp_timers.c new file mode 100644 index 0000000..1bbcce3 --- /dev/null +++ b/src/ptpd/src/ptp_timers.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 2015 Wojciech Owczarek, + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file timer.c + * @date Wed Oct 1 00:41:26 2014 + * + * @brief PTP timer handler code + * + * Glue code providing PTPd with event timers. + * This code can be re-written to make use of any timer + * implementation. Provided is an EventTimer which uses + * a fixed tick interval timer, or POSIX timers, depending + * on what is available. So the options are: + * - write another EventTimer implementation, + * using the existing framework, + * - write something different + */ + +#include "ptpd.h" + +void +timerStop(IntervalTimer * itimer) +{ + if (itimer == NULL) + return; + EventTimer *timer = (EventTimer *)(itimer->data); + + timer->stop(timer); +} + +void +timerStart(IntervalTimer * itimer, double interval) +{ + if (itimer == NULL) + return; + + if(interval > PTPTIMER_MAX_INTERVAL) { + interval = PTPTIMER_MAX_INTERVAL; + } + + itimer->interval = interval; + EventTimer* timer = (EventTimer *)(itimer->data); + + timer->start(timer, interval); +} + +Boolean +timerExpired(IntervalTimer * itimer) +{ + + if (itimer == NULL) + return FALSE; + + EventTimer *timer = (EventTimer *)(itimer->data); + + return timer->isExpired(timer); +} + +Boolean +timerRunning(IntervalTimer * itimer) +{ + + if (itimer==NULL) + return FALSE; + + EventTimer *timer = (EventTimer *)(itimer->data); + + return timer->isRunning(timer); +} + +Boolean timerSetup(IntervalTimer *itimers) +{ + + Boolean ret = TRUE; + +/* WARNING: these descriptions MUST be in the same order, + * and in the same number as the enum in ptp_timers.h + */ + + static const char* timerDesc[PTP_MAX_TIMER] = { + "PDELAYREQ_INTERVAL", + "DELAYREQ_INTERVAL", + "SYNC_INTERVAL", + "ANNOUNCE_RECEIPT", + "ANNOUNCE_INTERVAL", + "SYNC_RECEIPT", + "FOLLOWUP_RECEIPT", + "DELAY_RECEIPT", + "UNICAST_GRANT", + "OPERATOR_MESSAGES", + "LEAP_SECOND_PAUSE", + "STATUSFILE_UPDATE", + "PANIC_MODE", + "PERIODIC_INFO_TIMER", +#ifdef PTPD_STATISTICS + "STATISTICS_UPDATE", +#endif /* PTPD_STATISTICS */ + "ALARM_UPDATE", + "MASTER_NETREFRESH", + "CALIBRATION_DELAY", + "CLOCK_UPDATE", + "TIMINGDOMAIN_UPDATE" + }; + + int i = 0; + + startEventTimers(); + + for(i=0; itimingService; + ts = timingDomain.services[0]; + strncpy(ts->id, "PTP0", TIMINGSERVICE_MAX_DESC); + ts->dataSet.priority1 = rtOpts.preferNTP; + ts->dataSet.type = TIMINGSERVICE_PTP; + ts->config = &rtOpts; + ts->controller = ptpClock; + ts->timeout = rtOpts.idleTimeout; + ts->updateInterval = 1; + ts->holdTime = rtOpts.ntpOptions.failoverTimeout; + timingDomain.serviceCount = 1; + + if (rtOpts.ntpOptions.enableEngine) { + ntpSetup(&rtOpts, ptpClock); + } else { + timingDomain.serviceCount = 1; + timingDomain.services[1] = NULL; + } + + timingDomain.init(&timingDomain); + timingDomain.updateInterval = 1; + + startupInProgress = FALSE; + + /* global variable for message(), please see comment on top of this file */ + G_ptpClock = ptpClock; + + /* Proceed only if the Initialization of the SCore Ptpd is successful */ + if(SCORE_PTP_E_OK == Score_PtpInitialize(rtOpts.scoreConfig.GlobalTimePropagationDelay, + rtOpts.domainNumber, !rtOpts.slaveOnly)) + { + /* do the protocol engine */ + protocol(&rtOpts, ptpClock); + /* forever loop.. */ + } + + Score_PtpDeinitialize(); + + /* this also calls ptpd shutdown */ + timingDomain.shutdown(&timingDomain); + + NOTIFY("Self shutdown\n"); + + return 1; +} diff --git a/src/ptpd/src/ptpd.h b/src/ptpd/src/ptpd.h new file mode 100644 index 0000000..677925d --- /dev/null +++ b/src/ptpd/src/ptpd.h @@ -0,0 +1,500 @@ +/** + * @file ptpd.h + * @mainpage Ptpd v2 Documentation + * @authors Martin Burnicki, Alexandre van Kempen, Steven Kreuzer, + * George Neville-Neil + * @version 2.0 + * @date Fri Aug 27 10:22:19 2010 + * + * @section implementation Implementation + * PTPdV2 is not a full implementation of 1588 - 2008 standard. + * It is implemented only with use of Transparent Clock and Peer delay + * mechanism, according to 802.1AS requierements. + * + * This header file includes all others headers. + * It defines functions which are not dependant of the operating system. + */ + +// TODO: For QNX build, this needs to be replaced dynamically in BAZEL +#include + +#ifndef PTPD_H_ +#define PTPD_H_ + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + + +#ifdef linux +# ifndef _GNU_SOURCE +# define _GNU_SOURCE + #endif /* _GNU_SOURCE */ +#endif + +#ifdef __sun +# ifndef _XPG6 +# define _XPG6 +# endif /* _XPG6 */ +# ifndef _XOPEN_SOURCE +# define _XOPEN_SOURCE 500 +# endif /* _XOPEN_SOURCE */ +# ifndef __EXTENSIONS__ +# define __EXTENSIONS__ +# endif /* __EXTENSIONS */ +#endif /* __sun */ + +#include +#include +#include +#ifdef HAVE_STRINGS_H +#include +#endif /* HAVE_STRINGS_H */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIMEX_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_GETOPT_H +#include +#endif /* HAVE_GETOPT_H */ +#include +#include +#include +#include +#ifdef HAVE_UTMPX_H +#include +#else +#ifdef HAVE_UTMP_H +#include +#endif /* HAVE_UTMP_H */ +#endif /* HAVE_UTMPX_H */ + +#ifdef HAVE_NET_ETHERNET_H +#include +#endif /* HAVE_NET_ETHERNET_H */ + +#ifdef HAVE_UNIX_H /* setlinebuf() on QNX */ +#include +#endif /* HAVE_UNIX_H */ + +#include +#ifdef HAVE_NETINET_IN_SYSTM_H +#include +#endif +#include +#include +#ifdef HAVE_NETINET_ETHER_H +#include +#endif /* HAVE_NETINET_ETHER_H */ + +#ifdef HAVE_NET_IF_ARP_H +#include +#endif /* HAVE_NET_IF_ARP_H*/ + +#ifdef HAVE_NET_IF_H +#include +#endif /* HAVE_NET_IF_H*/ + +#ifdef HAVE_NETINET_IF_ETHER_H +#include +#endif /* HAVE_NETINET_IF_ETHER_H */ + + +#ifdef PTPD_PCAP +#ifdef HAVE_PCAP_PCAP_H +#include +#else +/* Cases like RHEL5 and others where only pcap.h exists */ +#ifdef HAVE_PCAP_H +#include +#endif /* HAVE_PCAP_H */ +#endif +#endif +#if defined(linux) && defined(HAVE_SCHED_H) +#include +#endif /* linux && HAVE_SCHED_H */ + +#ifdef HAVE_SYS_CPUSET_H +#include +#endif /* HAVE_SYS_CPUSET_H */ + +#include "constants.h" +#include "limits.h" + +/* Disable SO_TIMESTAMPING if configured to do so */ +#ifdef PTPD_DISABLE_SOTIMESTAMPING + +#ifdef SO_TIMESTAMPING + +#undef SO_TIMESTAMPING + +#endif /* SO_TIMESTAMPING */ + +#endif /* PTPD_DISABLE_SOTIMESTAMPING */ + +#include "dep/ipv4_acl.h" + +#include "dep/constants_dep.h" +#include "dep/datatypes_dep.h" + +#include "ptp_timers.h" +#include "dep/eventtimer.h" + +#include "dep/ntpengine/ntpdcontrol.h" +#include "dep/ntpengine/ntp_isc_md5.h" + +#include "timingdomain.h" + +#ifdef PTPD_STATISTICS +#include "dep/outlierfilter.h" +#endif + +#include "datatypes.h" + +#ifdef PTPD_STATISTICS +#include "dep/statistics.h" +#endif + +#include "dep/ptpd_dep.h" +#include "dep/iniparser/dictionary.h" +#include "dep/iniparser/iniparser.h" +#include "dep/daemonconfig.h" + +#include "dep/alarms.h" + +// ##### SCORE MODIFICATION BEGIN ##### +#include "inc/score_ptp_common.h" +// ##### SCORE MODIFICATION END ##### + + +/* NOTE: this macro can be refactored into a function */ +#define XMALLOC(ptr,size) \ + if(!((ptr)=malloc(size))) { \ + PERROR("failed to allocate memory"); \ + ptpdShutdown(ptpClock); \ + exit(1); \ + } + +#define SAFE_FREE(pointer) \ + if(pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } + +#define IS_SET(data, bitpos) \ + ((data & ( 0x1 << bitpos )) == (0x1 << bitpos)) + +#define SET_FIELD(data, bitpos) \ + data << bitpos + +#ifndef min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif /* min */ + +#ifndef max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif /* max */ + +#ifdef HAVE_LINUX_RTC_H +#include +#endif /* HAVE_LINUX_RTC_H */ + +#define SET_ALARM(alarm, val) \ + setAlarmCondition(&ptpClock->alarms[alarm], val, ptpClock) + +/** \name arith.c + * -Timing management and arithmetic*/ + /**\{*/ +/* arith.c */ + +/** + * \brief Convert Integer64 into TimeInternal structure + */ +void integer64_to_internalTime(Integer64,TimeInternal*); +/** + * \brief Convert TimeInternal structure to Integer64 + */ +void internalTime_to_integer64(TimeInternal, Integer64*); +/** + * \brief Convert TimeInternal into Timestamp structure (defined by the spec) + */ +void fromInternalTime(const TimeInternal*,Timestamp*); + +/** + * \brief Convert Timestamp to TimeInternal structure (defined by the spec) + */ +void toInternalTime(TimeInternal*, const Timestamp*); + +void ts_to_InternalTime(const struct timespec *, TimeInternal *); +void tv_to_InternalTime(const struct timeval *, TimeInternal *); + + + + +/** + * \brief Use to normalize a TimeInternal structure + * + * The nanosecondsField member must always be less than 10⁹ + * This function is used after adding or subtracting TimeInternal + */ +void normalizeTime(TimeInternal*); + +/** + * \brief Add two InternalTime structure and normalize + */ +void addTime(TimeInternal*,const TimeInternal*,const TimeInternal*); + +/** + * \brief Substract two InternalTime structure and normalize + */ +void subTime(TimeInternal*,const TimeInternal*,const TimeInternal*); +/** \}*/ + +/** + * \brief Divied an InternalTime by 2 + */ +void div2Time(TimeInternal *); + +/** \name bmc.c + * -Best Master Clock Algorithm functions*/ + /**\{*/ +/* bmc.c */ +/** + * \brief Compare data set of foreign masters and local data set + * \return The recommended state for the port + */ + +UInteger8 bmc(ForeignMasterRecord*, const RunTimeOpts*,PtpClock*); + +/* compare two portIdentTitties */ +int cmpPortIdentity(const PortIdentity *a, const PortIdentity *b); +/* check if portIdentity is all zero */ +Boolean portIdentityEmpty(PortIdentity *portIdentity); +/* check if portIdentity is all ones */ +Boolean portIdentityAllOnes(PortIdentity *portIdentity); + +/** + * \brief When recommended state is Master, copy local data into parent and grandmaster dataset + */ +void m1(const RunTimeOpts *, PtpClock*); + +/** + * \brief When recommended state is Slave, copy dataset of master into parent and grandmaster dataset + */ +void s1(MsgHeader*,MsgAnnounce*,PtpClock*, const RunTimeOpts *); + + +void p1(PtpClock *ptpClock, const RunTimeOpts *rtOpts); + +/** + * \brief Initialize datas + */ +void initData(RunTimeOpts*,PtpClock*); +/** \}*/ + + +/** \name protocol.c + * -Execute the protocol engine*/ + /**\{*/ +/** + * \brief Protocol engine + */ +/* protocol.c */ +void protocol(RunTimeOpts*,PtpClock*); +void updateDatasets(PtpClock* ptpClock, const RunTimeOpts* rtOpts); +void setPortState(PtpClock *ptpClock, Enumeration8 state); + +Boolean acceptPortIdentity(PortIdentity thisPort, PortIdentity targetPort); + +/** \}*/ + +/** \name management.c + * -Management message support*/ + /**\{*/ +/* management.c */ +/** + * \brief Management message support + */ +void handleManagement(MsgHeader *header, + Boolean isFromSelf, Integer32 sourceAddress, RunTimeOpts *rtOpts, PtpClock *ptpClock); + +/** \}*/ + +/** \name signaling.c + * -Signaling message support*/ + /**\{*/ +/* signaling.c */ +/** + * \brief Signaling message support + */ +UnicastGrantTable* findUnicastGrants(const PortIdentity* portIdentity, Integer32 TransportAddress, UnicastGrantTable *grantTable, UnicastGrantIndex *index, int nodeCount, Boolean update); +void initUnicastGrantTable(UnicastGrantTable *grantTable, Enumeration8 delayMechanism, int nodeCount, UnicastDestination *destinations, const RunTimeOpts *rtOpts, PtpClock *ptpClock); + +void cancelUnicastTransmission(UnicastGrantData*, const RunTimeOpts*, PtpClock*); +void cancelAllGrants(UnicastGrantTable *grantTable, int nodeCount, const RunTimeOpts *rtOpts, PtpClock *ptpClock); + +void handleSignaling(MsgHeader*, Boolean, Integer32, const RunTimeOpts*,PtpClock*); + +void refreshUnicastGrants(UnicastGrantTable *grantTable, int nodeCount, const RunTimeOpts *rtOpts, PtpClock *ptpClock); +void updateUnicastGrantTable(UnicastGrantTable *grantTable, int nodeCount, const RunTimeOpts *rtOpts); + + +/* quick shortcut to defining a temporary char array for the purpose of snprintf to it */ +#define tmpsnprintf(var,len, ...) \ + char var[len+1]; \ + memset(var, 0, len+1); \ + snprintf(var, len, __VA_ARGS__); + +/* + * \brief Packing and Unpacking macros + */ +#define DECLARE_PACK( type ) void pack##type( void*, void* ); + +DECLARE_PACK( NibbleUpper ) +DECLARE_PACK( Enumeration4Lower ) +DECLARE_PACK( UInteger4Lower ) +DECLARE_PACK( UInteger4Upper ) +DECLARE_PACK( UInteger16 ) +DECLARE_PACK( UInteger8 ) +DECLARE_PACK( Octet ) +DECLARE_PACK( Integer8 ) +DECLARE_PACK( UInteger48 ) +DECLARE_PACK( Integer64 ) + +#define DECLARE_UNPACK( type ) void unpack##type( void*, void*, PtpClock *ptpClock ); + +DECLARE_UNPACK( Boolean ) +DECLARE_UNPACK( Enumeration4Lower ) +DECLARE_UNPACK( Enumeration4Upper ) +DECLARE_UNPACK( Octet ) +DECLARE_UNPACK( UInteger48 ) +DECLARE_UNPACK( Integer64 ) + +/* display.c */ +void displayRunTimeOpts(const RunTimeOpts*); +void displayDefault (const PtpClock*); +void displayCurrent (const PtpClock*); +void displayParent (const PtpClock*); +void displayGlobal (const PtpClock*); +void displayPort (const PtpClock*); +void displayForeignMaster (const PtpClock*); +void displayOthers (const PtpClock*); +void displayBuffer (const PtpClock*); +void displayPtpClock (const PtpClock*); +void timeInternal_display(const TimeInternal*); +void clockIdentity_display(const ClockIdentity); +void netPath_display(const NetPath*); +void intervalTimer_display(const IntervalTimer*); +void integer64_display (const Integer64*); +void timeInterval_display(const TimeInterval*); +void portIdentity_display(const PortIdentity*); +void clockQuality_display (const ClockQuality*); +void PTPText_display(const PTPText*, const PtpClock*); +void iFaceName_display(const Octet*); +void unicast_display(const Octet*); +const char *portState_getName(Enumeration8 portState); +const char *getMessageTypeName(Enumeration8 messageType); +const char* accToString(uint8_t acc); +const char* delayMechToString(uint8_t mech); +void timestamp_display(const Timestamp * timestamp); + +void displayCounters(const PtpClock*); +void displayStatistics(const PtpClock*); +void clearCounters(PtpClock *); + +void msgHeader_display(const MsgHeader*); +void msgAnnounce_display(const MsgAnnounce*); +void msgSync_display(const MsgSync *sync); +void msgFollowUp_display(const MsgFollowUp*); +void msgPdelayReq_display(const MsgPdelayReq*); +void msgDelayReq_display(const MsgDelayReq * req); +void msgDelayResp_display(const MsgDelayResp * resp); +void msgPdelayResp_display(const MsgPdelayResp * presp); +void msgPdelayRespFollowUp_display(const MsgPdelayRespFollowUp * prespfollow); +void msgManagement_display(const MsgManagement * manage); +void msgSignaling_display(const MsgSignaling * signaling); + +void mMSlaveOnly_display(const MMSlaveOnly*, const PtpClock*); +void mMClockDescription_display(const MMClockDescription*, const PtpClock*); +void mMUserDescription_display(const MMUserDescription*, const PtpClock*); +void mMInitialize_display(const MMInitialize*, const PtpClock*); +void mMDefaultDataSet_display(const MMDefaultDataSet*, const PtpClock*); +void mMCurrentDataSet_display(const MMCurrentDataSet*, const PtpClock*); +void mMParentDataSet_display(const MMParentDataSet*, const PtpClock*); +void mMTimePropertiesDataSet_display(const MMTimePropertiesDataSet*, const PtpClock*); +void mMPortDataSet_display(const MMPortDataSet*, const PtpClock*); +void mMPriority1_display(const MMPriority1*, const PtpClock*); +void mMPriority2_display(const MMPriority2*, const PtpClock*); +void mMDomain_display(const MMDomain*, const PtpClock*); +void mMLogAnnounceInterval_display(const MMLogAnnounceInterval*, const PtpClock*); +void mMAnnounceReceiptTimeout_display(const MMAnnounceReceiptTimeout*, const PtpClock*); +void mMLogSyncInterval_display(const MMLogSyncInterval*, const PtpClock*); +void mMVersionNumber_display(const MMVersionNumber*, const PtpClock*); +void mMTime_display(const MMTime*, const PtpClock*); +void mMClockAccuracy_display(const MMClockAccuracy*, const PtpClock*); +void mMUtcProperties_display(const MMUtcProperties*, const PtpClock*); +void mMTraceabilityProperties_display(const MMTraceabilityProperties*, const PtpClock*); +void mMTimescaleProperties_display(const MMTimescaleProperties*, const PtpClock*); +void mMUnicastNegotiationEnable_display(const MMUnicastNegotiationEnable*, const PtpClock*); +void mMDelayMechanism_display(const MMDelayMechanism*, const PtpClock*); +void mMLogMinPdelayReqInterval_display(const MMLogMinPdelayReqInterval*, const PtpClock*); +void mMErrorStatus_display(const MMErrorStatus*, const PtpClock*); + +void sMRequestUnicastTransmission_display(const SMRequestUnicastTransmission*, const PtpClock*); +void sMGrantUnicastTransmission_display(const SMGrantUnicastTransmission*, const PtpClock*); +void sMCancelUnicastTransmission_display(const SMCancelUnicastTransmission*, const PtpClock*); +void sMAcknowledgeCancelUnicastTransmission_display(const SMAcknowledgeCancelUnicastTransmission*, const PtpClock*); + +void clearTime(TimeInternal *time); + +char *dump_TimeInternal(const TimeInternal * p); +char *dump_TimeInternal2(const char *st1, const TimeInternal * p1, const char *st2, const TimeInternal * p2); +const char * getTimeSourceName(Enumeration8 timeSource); + +int snprint_TimeInternal(char *s, int max_len, const TimeInternal * p); + +void nano_to_Time(TimeInternal *time, int nano); +int gtTime(const TimeInternal *x, const TimeInternal *b); +void absTime(TimeInternal *time); +int is_Time_close(const TimeInternal *x, const TimeInternal *b, int nanos); +int isTimeInternalNegative(const TimeInternal * p); +double timeInternalToDouble(const TimeInternal * p); +TimeInternal doubleToTimeInternal(const double d); + +uint32_t fnvHash(void *input, size_t len, int modulo); + +int check_timestamp_is_fresh2(const TimeInternal * timeA, const TimeInternal * timeB); +int check_timestamp_is_fresh(const TimeInternal * timeA); + + +void toState(UInteger8,const RunTimeOpts*,PtpClock*); + +/* helper functions for leap second handling */ +double secondsToMidnight(void); +double getPauseAfterMidnight(Integer8 announceInterval, int pausePeriod); + +Boolean respectUtcOffset(const RunTimeOpts * rtOpts, PtpClock * ptpClock); + +/* alarms.c - this will be moved */ +void capturePtpEventData(PtpEventData *data, PtpClock *ptpClock, RunTimeOpts *rtOpts); /* capture data from an alarm event */ +void setAlarmCondition(AlarmEntry *alarm, Boolean condition, PtpClock *ptpClock); /* set alarm condition and capture data */ + +#endif /*PTPD_H_*/ diff --git a/src/ptpd/src/ptpd2.8.in b/src/ptpd/src/ptpd2.8.in new file mode 100644 index 0000000..8efe288 --- /dev/null +++ b/src/ptpd/src/ptpd2.8.in @@ -0,0 +1,397 @@ +.\" -*- nroff -*" +.TH ptpd2 8 "@RELEASE_DATE@" "version @VERSION_NUMBER@" "Precision Time Protocol daemon" +.SH NAME +ptpd2 \- Precision Time Protocol daemon (1588-2008) +.SH SYNOPSIS +.B ptpd2 +\fB[ -?hH ]\fR +\fB[ -e \fISETTING\fB ]\fR +\fB[ -kvOLAl ]\fR +\fB[ -smMyEPanCV ]\fR +\fB[ -c \fIFILE\fB ]\fR +\fB[ -R \fIDIR\fB ]\fR +\fB[ -f \fIFILE\fB ]\fR +\fB[ -S \fIFILE\fB ]\fR +\fB[ -d \fIDOMAIN\fB ]\fR +\fB[ -u \fIADDRESS\fB ]\fR +\fB[ -r \fINUMBER\fB ]\fR +\fB-i \fIINTERFACE\fB\fR +.SH DESCRIPTION +PTPd is a daemon that implements the Precision Time Protocol (PTP) +Version 2 as defined by the IEEE 1588-2008 standard. PTP was developed +to provide very precise time coordination of LAN connected computers. +The daemon must run as +.B root +in order to be able to manipluate the system clock and use low port numbers. +PTPd is feature rich, supports IPv4 multicast, unicast and hybrid mode (mixed) operation, +as well as Ethernet mode. Even without hardware assistance, PTPd is able to achieve and +maintain sub-microsecond level timing precision and is able to withstand PTP Grandmaster +failovers, link failures and restarts with minimal impact to timing performance. +PTPd is lightweight, portable and currently supports Linux, FreeBSD +and Mac OS X and runs on multiple CPU architectures, 32-bit and 64-bit, including x86 and ARM. +.SH COMMAND-LINE CONFIGURATION +As of version 2.3.0, configuration file is the preferred mechanism for configuring PTPd, therefore +the options available as short (\fI-x\fR) and long options (\fI--xxxxx\fR) mostly provide basic control +over the daemon operation, and only provide the very basic PTP protocol settings. The rest of the settings (see \fBptpd2.conf(5)\fR) +can also be specified as command-line options, but they take the long \fI--key:section="value"\fR form. + +.SH BASIC DAEMON OPTIONS +.TP +\fB-c --config-file \fIPATH\fR +Path to configuration file (see \fBptpd2.conf(5)\fR) +.TP +\fB-k --check-config\fR +Check configuration and exit - return 0 if configuration is correct. +.TP +\fB-v --version\fR +Print version string and exit +.TP +\fB-h --help\fR +Show help screen +.TP +\fB-H --long-help\fR +Show detailed help for all settings and behaviours +.TP +\fB-e --explain \fISETTING\fR +Show help for a single setting (\fIsection:key\fR) +.TP +\fB-O --default-config\fR +Show default configuration and exit (output usable as a configuration file) +.TP +\fB-L --ignore-lock\fR +Skip lock file checks and locking (also \fIglobal:ignore_lock\fR) +.TP +\fB-A --auto-lock\fR +Use preset / port mode specific lock file names - useful when running multiple instances +.TP +\fB-l --lockfile\fR +Specify lock file path (also \fIglobal:lock_file\fR) +.TP +\fB-p --print-lockfile\fR +Print path to lock file and exit (useful for init scripts in combination with auto lock files) +.TP +\fB-R --lock-directory \fIDIR\fR +Directory to store lock files (also \fIglobal:lock_directory\fR) +.TP +\fB-f --log-file \fIPATH\fR +Path to log file (also \fIglobal:logfile\fR) +.TP +\fB-S --statistics-file \fIPATH\fR +Path to statistics file (also \fIglobal:statistics_file\fR) +.TP +\fB-T --show-templates +Display built-in configuration templates +.TP +\fB-t --templates \fI[name],[name],...\fR +Apply one or more configuration templates in this order (\fIsee man(5) ptpd2.conf\fR), +also see \fIptpengine:template_files\fR and the \ +.TP +\fB-S --statistics-file \fIPATH\fR +Path to statistics file (also \fIglobal:statistics_file\fR) + +.SH BASIC PTP PROTOCOL OPTIONS +.TP +\fB-i --interface \fIDEV\fR +\fIREQUIRED:\fRInterface to use - eth0, etc (also \fIptpengine:interface\fR) +.TP +\fB-d --domain \fINUMBER\fR +PTP domain number to become part of (also \fIptpengine:domain\fR) +.TP +\fB-s --slaveonly\fR +Slave only mode (also \fIptpengine:preset=slaveonly\fR) +.TP +\fB-m --masterslave\fR +Full IEEE 1588 implementation: master, slave when not best GM (also \fIptpengine:preset=masterslave\fR) +.TP +\fB-M --masteronly\fR +Master only mode: passive when not best GM (also \fIptpengine:preset=masteronly\fR) +.TP +\fB-y --hybrid\fR +Hybrid mode - mixed multicast and unicast operation (multicast for sync and announce, unicast +for delay request and response (also \fIptpengine:ip_mode=hybrid\fR) +.TP +\fB-U --unicast\fR +Unicast operation - (\fIalso ptpengine:ip_mode=unicast\fR). For a GM, unicast destinations +must be specified (\fI-u, --unicast-destinations, ptpengine:unicast_destinations\fR) unless +using unicast negotiation (\fI-g, --unicast-negotiation, ptpengine:unicast_negotiation=y\fR) +for delay request and response (also \fIptpengine:ip_mode=hybrid\fR). For a slave, unicast +destinations must be specified if not using unicast negotiation. +.TP +\fB-g --unicast-negotiation\fR +Enable unicast message delivery and interval negotiation usin signaling messages, as used +by the Telecom profile (also enables \fIptpengine:ip_mode=unicast\fR) +.TP +\fB-u --unicast-destinations \fIip/host, ip/host, ...\fR +List of unicast destinations - see \fI--unicast\fR +(also \fIptpengine:ip_mode=unicast\fR + \fIptpengine:unicast_destinations\fR) +\fB-E --e2e\fR +End to end delay mechanism (also \fIptpengine:delay_mechanism=E2E\fR) +.TP +\fB-E --p2p\fR +Peer to peer delay mechanism (also \fIptpengine:delay_mechanism=P2P\fR) +.TP +\fB-a --delay-override\fR +In slave state, override delay request interval announced by master (also \fIptpengine:log_delayreq_override\fR) - the value +of \fIptpengine:log_delayreq_interval\fR is used +.TP +\fB-r --delay-interval \fINUMBER\fR +Specify delay request message interval (log 2) - (also \fIptpengine:log_delayreq_interval\fR) +.TP +\fB-n --clock:no_adjust\fR +Do not adjust the clock (also \fIclock:no_adjust\fR) +.TP +\fB-D --debug\fR +Debug level (also \fIglobal:debug_level\fR) - only if compiled with RUNTIME_DEBUG +.TP +\fB-C --foreground\fR +Don't run in background (also \fIglobal:foreground=Y\fR) +.TP +\fB-V --verbose\fR +Run in foreground, log all the messages to standard output (also \fIglobal:verbose_foreground=Y\fR) + +.SH COMPATIBILITY OPTIONS + +.TP +PTPd supports the following options compatible with versions before 2.3.0: +.RS 8 +.TP 8 +\fB-b \fIDEV\fR +Network interface to use +.TP 8 +\fB-i \fINUMBER\fR +PTP domain number +.TP 8 +\fB-G\fR +\'Master mode with NTP\' (master only mode) +.TP 8 +\fB-W\fR +\'Master mode without NTP\' (master / slave mode) +.TP 8 +\fB-Y \fINUMBER\fR +Delay request interval (log 2) +.TP 8 +\fB-t\fR +Do not adjust the clock +.RE +.TP +\fBNOTE:\fR the above options are deprecated and will be removed in subsequent versions. Until then, their use will issue a warning. + + +.SH PTPD PORT STATES + +.RS 8 +.TP +\fIinit\fR +INITIALIZING +.TP +\fIflt\fR +FAULTY +.TP +\fIlstn_init\fR +LISTENING (first time) +.TP +\fIlstn_reset\fR +LISTENING (subsequent reset) +.TP +\fIpass\fR +PASSIVE (not best master, not announcing) +.TP +\fIuncl\fR +UNCALIBRATED +.TP +\fIslv\fR +SLAVE +.TP +\fIpmst\fR +PRE-MASTER +.TP +\fImst\fR +MASTER (active) +.TP +\fIdsbl\fR +DISABLED +.TP +\fI? (unk)\fR +UNKNOWN state +.RE + +.SH STATISTICS LOG FILE FORMAT + +When the statistics log is enabled (\fIptpengine:log_statistics\fR, verbose foreground mode or log file - \fIptpengine:statistics_file\fR), +a PTPd slave will log clock sync information upon the receipt of every Sync and Delay Response message. +When PTPd starts up or flushes the log, a comment line (starting with #) will be logged, containing the names +of all columns. The format of this log is a series of comma-separated values (CSV) and can be easily +imported into statistics tools and most spreadsheet software packages for analysis and graphing. +This log can get very large when running PTPd for longer periods of time and with high message rates, therefore to reduce +the number of messages logged, the \fIglobal:statistics_log_interval\fR setting can be used, to limit the log output +to one message per configured interval only. The size and maximum number of the statistics +log can also be controlled - (see \fBptpd2.conf(5)\fR). +.TP +The meaning of the columns is as follows: +.RS 8 +.TP 8 +\fBTimestamp\fR +Time when message received. This can take the form of text date / time or Unix timestamp (with fractional seconds), +or both (in which case an exra field is added), depending onthe \fIglobal:statistics_timestamp_format\fR setting (see \fBptpd2.conf(5)\fR). +When importing the log into plotting software, if the software can understand Unix time, it is best to +set the timestamp format to unix or both, as some software will not properly deal with the fractional part of the second when converting +the date and time from text. +.TP 8 +\fBState\fR +Port state (see \fBPTP PORT STATES\fR). +.TP 8 +\fBClock ID\fR +Port identity of the current best master, as defined by IEEE 1588. This will be the local clock's ID if +the local clock is the best master. Displayed as \fIclock_id/port(host)\fR +Port is the PTP clock port number, not to be confused with UDP ports. The clock ID is an EUI-64 64-bit +ID, usually converted from the 48-bit MAC address, by inserting 0xfffe between the lower and upper +half of the MAC address. PTPd will attempt to convert the clock ID back to MAC address and look up +the hostname from \fI/etc/ethers\fR (see \fBethers(5)\fR). Populating the ethers file will help the +administrator recognise the masters by familiar hostnames. +.TP 8 +\fBOne Way Delay\fR +Current value of one-way delay (or mean path delay) in seconds, calculated by PTPd in slave state +from the Delay Request - Delay Response message exchange. \fINote:\fR if this value remains at zero, +this usually means that no Delay Response messages are being received, likely due to a network issue. +.TP 8 +\fBOffset From Master\fR +Current value of offset from master in seconds - this is the main output of the PTP engine in slave +state, which is the input of the clock servo for clock corrections. This is the value typically +observed when estimating the slave performance. +.TP 8 +\fBSlave to Master\fR +Intermediate offset value (seconds) extracted from the Delay Request - Delay Response message exchange, used for +computing one-way delay. If the last value was rejected by a filter, the previous value will +be shown in the log. This value will also be zero if no Delay Response messages are being received. +.TP 8 +\fBMaster to Slave\fR +Intermediate offset value (seconds) extracted from the Sync messages, used for computing the offset from master. +If the last value was rejected by a filter, the previous value will be shown in the log. +.TP 8 +\fBObserved Drift\fR +The integral accumulator of the clock control PI servo model - frequency difference between the slave clock and +master clock. This value becomes stable when the clock offset has stabilised, and can be used (and is) to detect +clock stability. +.TP 8 +\fBLast Packet Received\fR +This field shows what message was received last - this will be S for Sync, D for Delay Response and P for Peer +Delay Response when using P2P delay mode. If a slave logs no D (or P) entries, this means it's not receiving +Delay Response messages, which could be a network issue. For two-step clocks, "S" will still be printed when +Follow-up was received. +.TP 8 +\fBSequence ID\fR +Sequence number of the last received message (Sync, Follow-Up, Delay Response, Peer Delay Response). Sequence +numbers in the statistics log file can help identify timing issues when analysing capture files; a change of +offset or path delay can be traced to a particular packet that matches the sequence ID. +.TP 8 +\fBOne Way Delay Mean\fR +One-way delay mean computed over the last sampling window. +.TP 8 +\fBOne Way Delay Std Dev\fR +One-way delay standard deviation computed over the last sampling window. +.TP 8 +\fBOffset From Master Mean\fR +Offset from master mean computed over the last sampling window. +.TP 8 +\fBOffset From Master Std Dev\fR +Offset from master standard deviation computed over the last sampling window. +.TP 8 +\fBObserved Drift Mean\fR +Observed drift / local clock frequency adjustment mean computed over the last sampling window. +.TP 8 +\fBObserved Drift Std Dev\fR +Observed drift / local clock frequency adjustment standard deviation computed over the last sampling window. +The lower the value, the less aggressively the clock is being controlled and therefore the more stable it is. +.TP 8 +\fBraw delayMS\fR +Raw (unfiltered) delayMS value - useful for evaluating outliers and filter performance. +.TP 8 +\fBraw delaySM\fR +Raw (unfiltered) delaySM value - useful for evaluating outliers and filter performance. +.RE + +\fBNOTE:\fR All the statistical measures (mean and std dev) will only be computed and displayed if PTPd was +built without --disable-statistics. The duration of the sampling period is controlled with the +\fIglobal:statistics_update_interval\fR setting - (see \fBptpd2.conf(5)\fR). + +.SH HANDLED SIGNALS +.TP +\fBPTPd handles the following signals:\fR +.RS 8 +.TP 8 +\fISIGHUP\fR +Reload configuration file (if used) and reopen log files +.TP 8 +\fISIGUSR1\fR +When in slave state, force clock step to current Offset from Master value +.TP 8 +\fISIGUSR2\fR +Dump all PTP protocol counters to current log target +(and clear if \fIptpengine:sigusr2_clears_counters\fR set) +.TP 8 +\fISIGINT|SIGTERM\fR +Clean exit - close logs and other open files, clean up lock file and exit. +.TP 8 +\fISIGKILL\fR +Force an unclean exit. +.RE +.SH EXIT CODES +Upon exit, ptpd2 returns \fB0\fR on success - either successfully started in daemon mode, or otherwise exited cleanly. +\fB0\fR is also returned when the \fI-k\fR (\fI--check-config\fR) +option is used and the configuration was correct. A non-zero exit code is returned on errors. +\fB3\fR is returned on lock file errors and when ptpd2 could not be started as daemon. +\fB2\fR is returned on memory allocation errors during startup. For all other error conditions such as +configuration errors, running ptpd2 in help mode or with no parameters, on self shutdown, +network startup errors and when attempting to run ptpd2 as non-root - \fB1\fR is returned. + +.SH SUPPORTED PLATFORMS AND ARCHITECTURES + +PTPd is fully supported on Linux and FreeBSD as this is what the core developers focus on. +OpenBSD and NetBSD are also supported, but get less developers' attention. +So is Max OS X, and as of PTPd 2.3.1 also OpenSolaris (11) derivatives (tested on OmniOS). +Sun's / Oracle's Solaris 11 has not been tested but in essence, it should work as intended. +Solaris 10 is NOT supported because it does not provide the SO_TIMESTAMP socket option. +It should theoretically be possible to use Solaris 10 using the \fIpf\fR facility as used by \fIsnoop\fR, +but there is currently no ongoing effort to acheive this. Patches for QNX/Neutrino have been provided, +but cannot yet officially be merged because of no availability of QNX to the developers. Some users +have ported PTPd to other RTOS, but this has not been merged either. + +As of 2.3.1, PTPd runs entirely in software and only relies on kernel and OS APIs, so +there are no hardware dependencies. Any little-endian or big-endian port of modern versions +of the supported OSes should work, but only x86 and ARM are actively tested. The only dependencies close to hardware +can be NIC drivers and how and if they impact software timestamping. + +.SH HARDWARE TIMESTAMPING SUPPORT + +As of 2.3.1, PTPd still does not support hardware timestamping. This functionality will appear +in the upcoming version 2.4 - potentially an interim version of 2.3.x may be delivered that will +support hardware clocks and timestamping on Linux. This is very much OS-specific and to a large extent, +hardware-specific. Linux has a PTP kernel API but not all hardware supports it. +Because PTPd supports multiple OS platforms, where hardware timestamping may use different mechanisms +on every platform, it has to be re-written in a modular way to allow this without unnecessarily increasing +code complexity, which already is a problem. + +.SH BUGS +As of ptpd 2.3.1, the (Open)Solaris (11) OS family is supported, but libpcap functionality is +currently broken - IPv4/pcap and Ethernet transports cannot be used on those systems. PTPd will compile and run, + but will not receive any data. + +Please report any bugs using the bug tracker on the SourceForge page: +http://sourceforge.net/projects/ptpd/ + +.SH SEE ALSO +.Xr ptpd2.conf 5 +ptpd2.conf(5) +.SH AUTHORS +.P +Gael Mace +.P +Alexandre Van Kempen +.P +Steven Kreuzer +.P +George Neville-Neil +.P +Wojciech Owczarek + +\fBptpd2(8)\fR man page was written by Wojciech Owczarek for ptpd 2.3.0 in November 2013 diff --git a/src/ptpd/src/ptpd2.conf.5.in b/src/ptpd/src/ptpd2.conf.5.in new file mode 100644 index 0000000..fb9de3a --- /dev/null +++ b/src/ptpd/src/ptpd2.conf.5.in @@ -0,0 +1,3100 @@ +.\" -*- nroff -*" +.TH ptpd2.conf 5 "@RELEASE_DATE@" "version @VERSION_NUMBER@" "PTPd config file" +.SH NAME +ptpd2.conf \- Precision Time Protocol daemon config file + +.SH CONFIGURATION FILE FORMAT +Settings in the PTPd configuration file are grouped into sections and take +the form of \fIsection:key="value"\fR variables. The configuration file can either +be formatted that way (preferred) or in .ini file style where a series of +\fIkey="value"\fR variables is grouped into sections using \fB[\fIsection\fB]\fR headers. +Every setting listed here can also be specified +as a command line parameter \fB(\fI--section:key=value\fB)\fR. Quotation marks are optional. +\fBNOTE:\fR the configuration file must end with a newline. + +.SH RELOADING CONFIGURATION +Only a small number of configuration file settings (SNMP, lock file configuration) requires a restart +of the PTPd process to take effect. All other settings can be changed while ptpd is running - configuration file +is reloaded and checked for changes when PTPd receives the SIGHUP signal. When reloading configuration, +PTPd will always attempt to test settings before applying them and once running, will never exit as +a result of configuration errors. If it does exit during config refresh, this is most likely a bug. + +.SH COMMAND-LINE PRIORITY +Any setting passed as a command line parameter will always take priority over the configuration file, +so once ptpd is running, those settings cannot be changed - a warning will be logged on every +attempt to change those settings using the configuration file. + +.SH CONFIGURATION SECTIONS +.B ptpengine +PTP protocol specific configuration +.TP +.B clock +Clock related settings +.TP +.B servo +Clock control PI servo configuration +.TP +.B global +Global configuration - logging, etc. +.TP +.B ntpengine +NTP control configuration +.TP +.B variables +User-defined variables + +.SH USER-DEFINED VARIABLES +To allow for easier management and automated generation of configuration, PTPd supports user variables, +which can be defined in the configuration file or in command line. They are defined as \fIvariables:[name]=[value]\fR, +or if using .ini style format, in the \fI[variables]\fR section. Once defined, a variable can be referred to +in the remaining configuration settings as \fI@name@\fR, and is substituted with the value of the variable + +\fBExample\fR: + +variables:instance=server15 + +global:status_file=/var/run/ptpd2.@instance@.status + +global:log_file=/var/run/ptpd2.@instance@.status + +\fBNote:\fR for the same effect, ptpd can be run from command line, such as \fI --config=/path/to/file --variables:instance=server15\fR + +.SH BUILT_IN VARIABLES +PTPd includes suppport for built-in variables, automatically defined. The following variables are automatically substituted: + +@pid@ - current PTPd process ID +@hostname@ - current host name + + +.SH CONFIGURATION TEMPLATES AND TEMPLATE FILES +As of version 2.3.1.1, ptpd enables the user to minimise the configuration effort for common scenarios, using built-in templates +and template files. A template is a named set of pre-defined settings whic are prepended before any other settings, so user can +still overwrite settings provided by the template. To use this feature, set \fIglobal:config_templates=[name],[name],...\fR in the +configuration file, or run ptpd with \fI--global:config_templates=[name],[name],...\fR. Multiple templates can be specified, +separated by comma, space or tab; they are applied in the order they are provided, so template settings override any overlapping +settings from previous templates specified. Templates can include \fIuser-defined variables\fR. + +A number of \fItemplate files\fR can also be supplied with the \fIglobal:template_files\fR setting +(comma, space or tab separated lis of file paths). The template files will be processed in the order they are provided in, +so for overlapping settings, the last template applied overrides settings applied by any previous +templates. PTPd will also try to load a default template file on startup: \fItemplates.conf\fR +from the default data directory: \fI@prefix@/share/@PACKAGE_NAME@/templates.conf\fR + +The template file is formatted in .ini style - each template is a section defined as +\fI[template-name]\fR, followed by a number of settings specified as \fIsection:setting\fR. + +\fBExample\fR: + +[my-template] + +global:verbose_foreground=Y + +ptpengine:preset=slaveonly + + +To see the list of available built-in templates, run ptpd with \fI-T\fR or \fI--show-templates\fR + +.SH CONFIGURATION VARIABLES +.RS 0 +.TP 8 +\fBptpengine:interface [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Network interface to use - eth0, igb0 etc. (\fBrequired\fR). See also \fIptpengine:backup_interface\fR. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:backup_interface [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Backup network interface to use - eth0, igb0 etc. When no GM available, +slave will keep alternating between primary and secondary until a GM is found. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:preset [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fInone slaveonly masteronly masterslave \fR +.TP 8 +\fBusage\fR +PTP engine preset: +.RS 12 +.TP 12 +\fInone\fR +Defaults, no clock class restrictions +.TP 12 +\fIslaveonly\fR +Slave only (clock class 255 only) +.TP 12 +\fImasteronly\fR +Master, passive when not best master (clock class 0..127) +.TP 12 +\fImasterslave\fR +Full IEEE 1588 implementation: Master, slave when not best master (clock class 128..254) +.RE +.TP 8 +\fBdefault\fR +\fIslaveonly\fR +.TP 8 +\fBNOTE:\fR +Presets affect the following settings: \fIptpengine:slave_only\fR, \fIclock_no_adjust\fR and \fIptpengine:clock_class\fR (range and default value). +To see all preset settings, run ptpd2 \fI-H\fR (\fI--long-help\fR) + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:transport [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIipv4 ethernet\fR +.TP 8 +\fBusage\fR +Transport type for PTP packets. \fBNOTE:\fR Ethernet transport requires building with \fIlibpcap\fR and is not supported on Solaris as of 2.3.1, +and cannot be enabled on those systems unless ptpd is compiled with \fB--enable-experimental-options\fR. +.TP 8 +\fBdefault\fR +\fIipv4\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:dot1as [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable IEEE 802.1AS / AVB compatibility (transportSpecific field in PTP message headers). +Requires Ethernet transport as this is the only mapping used by 802.1AS that PTP supports +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ip_mode [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fImulticast unicast hybrid \fR +.TP 8 +\fBusage\fR +IP transmission mode (requires IP transport): +.RS 12 +.TP 12 +\fImulticast\fR +uses multicast for all messages +.TP 12 +\fIhybrid\fR +uses multicast for sync and announce, and unicast for delay request and response +.TP 12 +\fIunicast\fR +uses unicast for all transmission. When unicast mode is selected, destination IP(s) (\fIptpengine:unicast_ +destinations\fR) must be configured +depending on unicast negotiation setting (\fIptpengine:unicast_negotiation\fR) and master or slave role +(see: \fIptpengine:unicast_destinations\fR) +.RE +.TP 8 +\fBdefault\fR +\fImulticast\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:disabled [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Disable PTP port. Causes the PTP state machine to stay in PTP_DISABLED state indefinitely, +until it is re-enabled via configuration change or ENABLE_PORT management message. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_negotiation [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable unicast negotiation support using signaling messages - as used by the Telecom profile +(ITU-T G.8265.1). +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_grant_duration [\fIINT\fB: 30 .. 604800]\fR +.RS 8 +.TP 8 +\fBusage\fR +Duration (seconds) for which the transmission of unicast messages is granted by a master, +or requested by a slave when unicast negotiation is used (\fIptpengine:unicast_negotiation\fR). +When using PTPd with other PTP implementations, PTPd will never refuse to grant a message based +on the requested duration: it will grant for 30 seconds if requested for any less than 30 seconds, +and will grant for 7 days (604800) if requested for any longer. +.TP 8 +\fBdefault\fR +\fI300\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:disable_bmca [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Disable Best Master Clock Algorithm for unicast masters. Only effective for masteronly preset - +all Announce messages will be ignored and the cock will transition directly into MASTER state +and remain an active master. This behaviour is required for Telecom profile operation. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_any_master [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +When using unicast negotiation (slave), accept PTP messages from any grandmaster. +By default, only messages from acceptable masters (\fIptpengine:unicast_destinations\fR) +are accepted, and only if transmission was granted by the GM. This setting can be used +when mixing GMs supporting G.8265.1 and manual unicast (no negotiation), or to assist +with interoperability issues where signaling messages and timing messages come from +different port identities. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_port_mask [\fIINT\fB: 0 .. 65535 (0xFFFF)]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP port number wildcard mask (16-bit) applied onto port identities when running unicast negotiation: +allows multiple port identities (with the same clock ID) to be accepted as coming from the same port. +This option can be used as a workaround where a node sends signaling messages and timing messages +with different port identities. \fBNOTE:\fR This can also be entered in hexadecimal notation (0xNNNN). +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:disable_udp_checksums [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Disable UDP checksum validation on UDP sockets (Linux only). Workaround for situations where a node +(like Transparent Clock) does not rewrite checksums. Enabled by default. +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:use_libpcap [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Use libpcap for sending and receiving traffic (automatically enabled in Ethernet mode). +Requires building with libpcap - builds made with \fB--disable-pcap\fR cannot use this feature, and as of 2.3.1, Solaris systems will +not attempt to use libpcap unless compiled with \fB--enable-experimental-options\fR +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_mechanism [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIE2E P2P DELAY_DISABLED \fR +.TP 8 +\fBusage\fR +Delay detection mechanism used - use DELAY_DISABLED for syntonisation only (no synchronisation). E2E uses Delay Request messages, +P2P uses Peer Delay Request messages. +.TP 8 +\fBdefault\fR +\fIE2E\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:domain [\fIINT\fB: 0 .. 127]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP domain number. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:any_domain [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Usability extension: if enabled, a slave-only clock will accept masters from any domain, +while preferring the configured domain, and preferring lower domain number. This option should be +used for slave-only clocks and should not be used with unicast negotiation. +\fBNOTE:\fR this behaviour is not part of the standard. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:port_number [\fIINT\fB: 1 .. 65534]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP port number (part of PTP Port Identity - not UDP port). +For ordinary clocks (single port), the default should be used, +but when running multiple instances to simulate a boundary clock, +The port number can be changed. +.TP 8 +\fBdefault\fR +\fI1\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:port_description [\fISTRING: 64 characters max\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +User description of the PTP port - this value is returned in response to USER_DESCRIPTION management message and +CLOCK_DESCRIPTION management message. +.TP 8 +\fBdefault\fR +\fI[ptpd]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:slave_only [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Slave only mode (sets clock class to 255, overriding value from preset). +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:inbound_latency [\fIINT\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify latency correction (nanoseconds) for incoming packets. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:outbound_latency [\fIINT\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify latency correction (nanoseconds) for outgoing packets. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:offset_shift [\fIINT\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Apply an arbitrary shift (nanoseconds) to offset from master when in slave state. Value can be positive or negative - useful for correcting for of antenna latencies, delay assymetry and IP stack latencies. This will not be visible in the offset from master value - only in the resulting clock correction. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:always_respect_utc_offset [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Compatibility option: In slave state, always respect UTC offset announced by best master, even if the the + currrentUtcOffsetValid flag is announced FALSE. \fBNOTE\fR: this behaviour is not part of the standard. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:prefer_utc_offset_valid [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Compatibility extension to BMC algorithm: when enabled, BMC for both master and save clocks will prefer masters announcing currrentUtcOffsetValid as TRUE. + \fBNOTE\fR: this behaviour is not part of the standard. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:require_utc_offset_valid [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Compatibility option: when enabled, ptpd2 will ignore Announce messages from masters announcing currentUtcOffsetValid as FALSE. \fBNOTE\fR: this behaviour is not part of the standard. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_announce_interval [\fIINT\fB: -4 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP announce message interval in master state. When using unicast negotiation (\fIptpengine:unicast_negotiation\fR), +for slaves this is the initial (minimum) interval requested and for masters this is the minimum interval granted. +(expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +.TP 8 +\fBdefault\fR +\fI1\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_announce_interval_max [\fIINT\fB: -1 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +When using unicast negtiation (\fIptpengine:unicast_negotiation\fR), this is the maximum announce +interval granted by a master, and the maximum interval a slave will attempt to request. +(expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:announce_receipt_timeout [\fIINT\fB: 2 .. 255]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP announce receipt timeout announced in master state. +.TP 8 +\fBdefault\fR +\fI6\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:announce_receipt_grace_period [\fIINT\fB: 0 .. 20]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP announce receipt timeout grace period in slave state: when announce receipt timeout occurs, disqualify current best GM, + then wait n times announce receipt timeout before resetting. Allows for a seamless GM failover when standby GMs are slow + to react. When set to 0, this option is not used. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_sync_interval [\fIINT\fB: -7 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP sync message interval in master state. When using unicast negotiation (\fIptpengine:unicast_negotiation\fR), +for slaves this is the initial (minimum) interval requested and for masters this is the minimum interval granted. + (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_sync_interval_max [\fIINT\fB: -1 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +When using unicast negtiation (\fIptpengine:unicast_negotiation\fR), this is the maximum sync +interval granted by a master, and the maximum interval a slave will attempt to request. +(expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_delayreq_override [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Override the Delay Request interval provided by best master. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_delayreq_auto [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Automatically override the Delay Request interval (with \fI ptpengine:log_delayreq_interval\fR) +if the received value is 127 (0X7F), such as in unicast messages, +unless using unicast negotiation (\fIptpengine:unicast_negotiation\fR) +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_delayreq_interval_initial [\fIINT\fB: -7 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay request interval used before receiving first delay response + (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_delayreq_interval [\fIINT\fB: -7 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +Minimum delay request interval announced when in master state, in slave state overrides the master interval. + (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.). When using unicast negotiation (\fIptpengine:unicast_negotiation\fR), +for slaves this is the initial (minimum) interval requested and for masters this is the minimum interval granted. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_delayreq_interval_max [\fIINT\fB: -1 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +When using unicast negtiation (\fIptpengine:unicast_negotiation\fR), this is the maximum delay request +interval granted by a master, and the maximum interval a slave will attempt to request. + (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.). +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_peer_delayreq_interval [\fIINT\fB: -7 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +Minimum peer delay request message interval in peer to peer delay mode +(expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.). When using unicast negotiation (\fIptpengine:unicast_negotiation\fR), +this is the initial (minimum) interval requested by a node from its peer and this is the minimum interval granted for a peer. +.TP 8 +\fBdefault\fR +\fI1\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:log_peer_delayreq_interval_max [\fIINT\fB: -1 .. 7]\fR +.RS 8 +.TP 8 +\fBusage\fR +When using unicast negtiation (\fIptpengine:unicast_negotiation\fR), this is the maximum peer delay request +interval granted by a node, and the maximum interval a node will attempt to request from its peer. + (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.). +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:foreignrecord_capacity [\fIINT\fB: 5 .. 10]\fR +.RS 8 +.TP 8 +\fBusage\fR +Foreign master record size (Maximum number of foreign masters). +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ptp_allan_variance [\fIINT\fB: 0 .. 65535]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify Allan variance announced in master state. +.TP 8 +\fBdefault\fR +\fI28768\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ptp_clock_accuracy [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIACC_25NS ACC_100NS ACC_250NS ACC_1US ACC_2.5US ACC_10US ACC_25US ACC_100US ACC_250US ACC_1MS ACC_2.5MS ACC_10MS ACC_25MS ACC_100MS ACC_250MS ACC_1S ACC_10S ACC_10SPLUS ACC_UNKNOWN \fR +.TP 8 +\fBusage\fR +Clock accuracy range announced in master state. +.TP 8 +\fBdefault\fR +\fIACC_UNKNOWN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:utc_offset [\fIINT\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Underlying time source UTC offset announced in master state. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:utc_offset_valid [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Underlying time source UTC offset validity announced in master state. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:time_traceable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Underlying time source time traceability announced in master state. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:frequency_traceable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Underlying time source frequency traceability announced in master state. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ptp_timescale [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIPTP ARB \fR +.TP 8 +\fBusage\fR +Time scale announced in master state (with ARB, UTC properties are ignored by slaves). When clock class is set to 13 (application specific), this value is ignored and ARB is used. +.TP 8 +\fBdefault\fR +\fIARB\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ptp_timesource [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIATOMIC_CLOCK GPS TERRESTRIAL_RADIO PTP NTP HAND_SET OTHER INTERNAL_OSCILLATOR \fR +.TP 8 +\fBusage\fR +Time source announced in master state. +.TP 8 +\fBdefault\fR +\fIINTERNAL_OSCILLATOR\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:clock_class [\fIINT\fB: 0 .. 255]\fR +.RS 8 +.TP 8 +\fBusage\fR +Clock class - announced in master state. Always 255 for slave-only. +Minimum, maximum and default values are controlled by presets. +If set to 13 (application specific time source), announced time scale is always set to ARB. +This setting controls the states a PTP port can be in. If below 128, port will only be in MASTER or PASSIVE states (master only). If above 127, port will be in MASTER or SLAVE states. +.TP 8 +\fBdefault\fR +\fI255\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:priority1 [\fIINT\fB: 0 .. 248]\fR +.RS 8 +.TP 8 +\fBusage\fR +Priority 1 announced in master state,used for Best Master + Clock selection. +.TP 8 +\fBdefault\fR +\fI128\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:priority2 [\fIINT\fB: 0 .. 248]\fR +.RS 8 +.TP 8 +\fBusage\fR +Priority 2 announced in master state, used for Best Master + Clock selection. +.TP 8 +\fBdefault\fR +\fI128\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:max_listen [\fIINT\fB: min: 1 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Number of consecutive protocol resets to LISTENING before full network reset. +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_destinations [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +An IPv4 address or list of IPv4 addresses to be used as unicast destinations. +When unicast negotiation (\fIptpengine:unicast_negotiation\fR) is enabled, setting this +is mandatory for slaves as they must be aware of which GMs to request messages from. +When unicast negotiation is disabled, setting this is mandatory for GMs, as they must +deliver messages to a pre-configured group of slaves. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_domains [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify PTP domain number for each configured unicast destination (\fIptpengine:unicast_destinations\fR). +This is only used by slave-only clocks using multiple unicast destinations to allow for each master +to be in a separate domain, such as with Telecom Profile. The number of entries should match the number +of unicast destinations, otherwise unconfigured domains or domains set to 0 are set to domain configured in \fIptpengine:domain\fR. +The format is a comma, tab or space-separated list of 8-bit unsigned integers (0 .. 255). +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_local_preference [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify a local preference for each configured unicast destination (\fIptpengine:unicast_destinations\fR). +This is only used by slave-only clocks using multiple unicast destinations to allow for each master's +BMC selection to be influenced locally by the slave, such as with Telecom Profile. The number of entries should match the number +of unicast destinations, otherwise unconfigured preference is set to 255 (lowest), so that the unconfigurest entries do not +pre-empt the configured entries. The format is a comma, tab or space-separated list of 8-bit unsigned integers (0 .. 255). +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:unicast_peer_destination [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +When using IP unicast mode (\fIptpengine:ip_mode=unicast\fr) and Peer to Peer delay mechanism +(\fIptpengine:delay_mechanism=P2P\fR), a peer unicast destination must be configured to request +the peer delay from. Format is a single unicast IPv4 address. +.TP 8 +\fBdefault\fR +\fI[none]\fR + + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:management_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable handling of PTP management messages. Only GET messages are processed by default. +See \fIptpengine:management_set_enable\fR. +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:management_set_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Accept SET and COMMAND management messages. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:igmp_refresh [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Send explicit IGMP joins between engine resets and periodically + in master state. +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:master_igmp_refresh_interval [\fIINT\fB: 0 .. 255]\fR +.RS 8 +.TP 8 +\fBusage\fR +Periodic IGMP join interval (seconds) in master state when running +IPv4 multicast: when set below 10 or when ptpengine:igmp_refresh +is disabled, this setting has no effect. +.TP 8 +\fBdefault\fR +\fI60\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:multicast_ttl [\fIINT\fB: 1 .. 64]\fR +.RS 8 +.TP 8 +\fBusage\fR +Multicast time to live for multicast PTP packets (ignored and set to 1 +for peer to peer messages). +.TP 8 +\fBdefault\fR +\fI64\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ip_dscp [\fIINT\fB: 0 .. 63]\fR +.RS 8 +.TP 8 +\fBusage\fR +DiffServ CodepPoint for packet prioritisation (decimal). When set to zero, +this option is not used. Use 46 for Expedited Forwarding (0x2e). +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_stat_filter_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable statistical filter for Sync messages +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_stat_filter_type [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fInone mean min max absmin absmax median \fR +.TP 8 +\fBusage\fR +Type of filter used for Sync message filtering: +.RS 12 +.TP 12 +\fInone\fR +no filtering - pass-through +.TP 12 +\fImean\fR +mean (average) - smooth results but influenced by outliers +.TP 12 +\fImin\fR +minimal value - useful for high packet delay variation ("lucky packets") +.TP 12 +\fImax\fR +maximal value - useful for testing worst case scenarios +.TP 12 +\fIabsmin\fR +absolute minimum - value closest to zero. Also useful for test purposes. +.TP 12 +\fIabsmax\fR +absolute maximun value farthest away from zero +.TP 12 +\fImedian\fR +median (middle value) - more robust than mean, not influenced by outliers +.RE +.TP 8 +\fBdefault\fR +\fImin\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_stat_filter_window [\fIINT\fB: 3 .. 128]\fR +.RS 8 +.TP 8 +\fBusage\fR +Number of samples used for the Sync statistical filter +.TP 8 +\fBdefault\fR +\fI4\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_stat_filter_window_type [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIsliding interval\fR +.TP 8 +\fBusage\fR +Sampling window behaviour for the Sync statistical filter: +.RS 12 +.TP 12 +\fIsliding\fR +sliding window - a value is output every time the filter runs, which can result in duplicates +.TP 12 +\fIinterval\fR +only output a value every n-th sample (full window) - independent sampling periods +.RE +.TP 8 +\fBdefault\fR +\fIsliding\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_stat_filter_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable statistical filter for Delay messages +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_stat_filter_type [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fInone mean min max absmin absmax median \fR +.TP 8 +\fBusage\fR +Type of filter used for Delay message filtering: +.RS 12 +.TP 12 +\fInone\fR +no filtering - pass-through +.TP 12 +\fImean\fR +mean (average) - smooth results but influenced by outliers +.TP 12 +\fImin\fR +minimal value - useful for high packet delay variation ("lucky packets") +.TP 12 +\fImax\fR +maximal value - useful for testing worst case scenarios +.TP 12 +\fIabsmin\fR +absolute minimum - value closest to zero. Also useful for test purposes. +.TP 12 +\fIabsmax\fR +absolute maximun value farthest away from zero +.TP 12 +\fImedian\fR +median (middle value) - more robust than mean, not influenced by outliers +.RE +.TP 8 +\fBdefault\fR +\fImin\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_stat_filter_window [\fIINT\fB: 3 .. 128]\fR +.RS 8 +.TP 8 +\fBusage\fR +Number of samples used for the Delay statistical filter +.TP 8 +\fBdefault\fR +\fI4\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_stat_filter_window_type [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIsliding interval\fR +.TP 8 +\fBusage\fR +Sampling window behaviour for the Delay statistical filter: +.RS 12 +.TP 12 +\fIsliding\fR +sliding window - a value is output every time the filter runs, which can result in duplicates +.TP 12 +\fIinterval\fR +only output a value every n-th sample (full window) - independent sampling periods +.RE +.TP 8 +\fBdefault\fR +\fIsliding\fR + + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable outlier filter for the Delay Response component in slave state +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_action [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIdiscard filter \fR +.TP 8 +\fBusage\fR +Delay Response outlier filter action. If set to 'filter', outliers are + replaced with moving average. +.TP 8 +\fBdefault\fR +\fIdiscard\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_capacity [\fIINT\fB: 4 .. 60]\fR +.RS 8 +.TP 8 +\fBusage\fR +Number of samples in the Delay Response outlier filter buffer +.TP 8 +\fBdefault\fR +\fI20\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_threshold [\fIFLOAT\fB: 0.001000 .. 1000.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay Response outlier filter threshold: multiplier for Peirce's maximum + standard deviation. When set below 1.0, filter is tighter, when set above + 1.0, filter is looser than standard Peirce's test. +.TP 8 +\fBdefault\fR +\fI1.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_always_filter [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Always run the Delay Response outlier filter, even if clock is being slewed at maximum rate +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_autotune_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable automatic threshold control for Delay Response outlier filter. +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_autotune_minpercent [\fIINT\fB: 0 .. 99]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay Response outlier filter autotune low watermark - minimum percentage +of discarded samples in the update period before filter is tightened +by the autotune step value +.TP 8 +\fBdefault\fR +\fI20\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_autotune_maxpercent [\fIINT\fB: 1 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay Response outlier filter autotune high watermark - maximum percentage +of discarded samples in the update period before filter is loosened +by the autotune step value +.TP 8 +\fBdefault\fR +\fI95\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_autotune_step [\fIFLOAT\fB: 0.010000 .. 10.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +The value the Delay Response outlier filter threshold is increased +or decreased by when auto-tuning +.TP 8 +\fBdefault\fR +\fI0.100000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_autotune_minthreshold [\fIFLOAT\fB: 0.010000 .. 10.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Minimum Delay Response filter threshold value used when auto-tuning +.TP 8 +\fBdefault\fR +\fI0.100000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_autotune_maxthreshold [\fIFLOAT\fB: 0.010000 .. 10.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum Delay Response filter threshold value used when auto-tuning +.TP 8 +\fBdefault\fR +\fI5.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_weight [\fIFLOAT\fB: 0.010000 .. 2.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay Response outlier weight: if an outlier is detected, determines + the amount of its deviation from mean that is used to build the standard + deviation statistics and influence further outlier detection. + When set to 1.0, the outlier is used as is. +.TP 8 +\fBdefault\fR +\fI1.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_stepdetect_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable Delay Response filter step detection (delaySM) to block when certain level exceeded +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_stepdetect_threshold [\fIINT\fB: 50000 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay step detection threshold. Step detection is performed only +when delaySM is below this threshold (nanoseconds) +.TP 8 +\fBdefault\fR +\fI1000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_stepdetect_level [\fIINT\fB: 50000 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay step level. When step detection enabled and operational, delaySM above this level +(nanoseconds) is considered a clock step and updates are paused +.TP 8 +\fBdefault\fR +\fI500000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_stepdetect_credit [\fIINT\fB: 50 .. 1000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Initial credit (number of samples) the Delay step detection filter can block for. +When credit is exhausted, filter stops blocking. Credit is gradually restored +(see \fIptpengine:delay_outlier_filter_stepdetect_credit_increment\fR) +.TP 8 +\fBdefault\fR +\fI200\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:delay_outlier_filter_stepdetect_credit_increment [\fIINT\fB: 1 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Amount of credit for the Delay step detection filter restored every full sample window +.TP 8 +\fBdefault\fR +\fI10\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable outlier filter for the Sync component in slave state. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_action [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIdiscard filter \fR +.TP 8 +\fBusage\fR +Sync outlier filter action. If set to 'filter', outliers are replaced + with moving average. +.TP 8 +\fBdefault\fR +\fIdiscard\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_capacity [\fIINT\fB: 4 .. 60]\fR +.RS 8 +.TP 8 +\fBusage\fR +Number of samples in the Sync outlier filter buffer. +.TP 8 +\fBdefault\fR +\fI20\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_threshold [\fIFLOAT\fB: 0.001000 .. 1000.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Sync outlier filter threshold: multiplier for the Peirce's maximum standard + deviation. When set below 1.0, filter is tighter, when set above 1.0, + filter is looser than standard Peirce's test. +.TP 8 +\fBdefault\fR +\fI1.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_always_filter [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Always run the Sync outlier filter, even if clock is being slewed at maximum rate +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_autotune_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable automatic threshold control for Sync outlier filter. +.TP 8 +\fBdefault\fR +\fIY\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_autotune_minpercent [\fIINT\fB: 0 .. 99]\fR +.RS 8 +.TP 8 +\fBusage\fR +Sync outlier filter autotune low watermark - minimum percentage +of discarded samples in the update period before filter is tightened +by the autotune step value +.TP 8 +\fBdefault\fR +\fI20\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_autotune_maxpercent [\fIINT\fB: 1 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Sync outlier filter autotune high watermark - maximum percentage +of discarded samples in the update period before filter is loosened +by the autotune step value +.TP 8 +\fBdefault\fR +\fI95\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_autotune_step [\fIFLOAT\fB: 0.010000 .. 10.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +The value the Sync outlier filter threshold is increased +or decreased by when auto-tuning +.TP 8 +\fBdefault\fR +\fI0.100000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_autotune_minthreshold [\fIFLOAT\fB: 0.010000 .. 10.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Minimum Sync filter threshold value used when auto-tuning +.TP 8 +\fBdefault\fR +\fI0.100000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_autotune_maxthreshold [\fIFLOAT\fB: 0.010000 .. 10.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum Sync filter threshold value used when auto-tuning +.TP 8 +\fBdefault\fR +\fI5.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_weight [\fIFLOAT\fB: 0.010000 .. 2.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Sync outlier weight: if an outlier is detected, this value determines the + amount of its deviation from mean that is used to build the standard + deviation statistics and influence further outlier detection. + When set to 1.0, the outlier is used as is +.TP 8 +\fBdefault\fR +\fI1.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_stepdetect_enable [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable Sync filter step detection (delayMS) to block when certain level exceeded +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_stepdetect_threshold [\fIINT\fB: 50000 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Sync step detection threshold. Step detection is performed only +when delayMS is below this threshold (nanoseconds) +.TP 8 +\fBdefault\fR +\fI1000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_stepdetect_level [\fIINT\fB: 50000 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Sync step level. When step detection enabled and operational, delayMS above this level +(nanoseconds) is considered a clock step and updates are paused +.TP 8 +\fBdefault\fR +\fI500000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_stepdetect_credit [\fIINT\fB: 50 .. 1000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Initial credit (number of samples) the Sync step detection filter can block for. +When credit is exhausted, filter stops blocking. Credit is gradually restored +(see \fIptpengine:sync_outlier_filter_stepdetect_credit_increment\fR) +.TP 8 +\fBdefault\fR +\fI200\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_outlier_filter_stepdetect_credit_increment [\fIINT\fB: 1 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Amount of credit for the Sync step detection filter restored every full sample window +.TP 8 +\fBdefault\fR +\fI10\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sync_sequence_checking [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +When enabled, Sync messages will only be accepted if sequence ID is increasing. +\fBnote:\fR This can cause the slave to temporarily lock up if GM restarts before announce timeout, +so this is limited to 50 consecutive sequence errors. Alternatively, \fIptpengine:clock_update_timeout\fR +can be used to reset the slave beforehand. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:clock_update_timeout [\fIINT\fB: 0 .. 3600]\fR +.RS 8 +.TP 8 +\fBusage\fR +If set to non-zero, time (seconds) before slave is reset back into PTP_LISTENING, if thetre +were no clock updates. This is useful for situations where slave is in SLAVE state +(receiving Announce) but is not receiving or not accepting Sync messages. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:calibration_delay [\fIINT\fB: 0 .. 300]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay between moving to slave state and enabling clock updates (seconds). +This allows mean path delay to stabilise before starting clock updates. +Activated when going into slave state and during slave's GM failover. +0 - not used. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:idle_timeout [\fIINT\fB: 10 .. 3600]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP idle timeout (seconds): if PTPd is in SLAVE state and there have been no clock +updates for this amout of time, PTPd releases clock control.\n +.TP 8 +\fBdefault\fR +\f60\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:offset_alarm_threshold [\fIINT\fB: 0 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +PTP Slave Offset from Master alarm threshold (nanoseconds) - absolute value. When set to non-zero, an alarm is raised +when PTP slave's offset from master crosses this value. The alarm is logged, indicated in the status file, and SNMP +traps are sent if SNMP is enabled. Similar notifications are created when offset returns within the threshold. +When zet to 0, offset is not checked against the threshold. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:panic_mode [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable panic mode: when offset from master is above 1 second, stop updating +the clock for a period of time and then step the clock if offset remains +above 1 second. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:panic_mode_duration [\fIINT\fB: 1 .. 60]\fR +.RS 8 +.TP 8 +\fBusage\fR +Duration (minutes) of the panic mode period (no clock updates) when offset +above 1 second detected. +.TP 8 +\fBdefault\fR +\fI2\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:panic_mode_release_clock [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +When entering panic mode, release clock control while panic mode lasts. +If not set, PTP will hold clock control during panic mode. +If set together with ntpengine:* configured, this will fail over to NTP. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:panic_mode_exit_threshold [\fIINT\fB: 0 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Do not exit panic mode until offset drops below this value (nanoseconds). +0 = not used. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:pid_as_clock_identity [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Use PTPd's process ID as the middle part of the PTP clock ID - useful for running multiple instances. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ntp_failover [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Fail over to NTP when PTP time sync not available - requires +ntpengine:enabled, but does not require the rest of NTP configuration: +will warn instead of failing over if cannot control ntpd. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:ntp_failover_timeout [\fIINT\fB: 0 .. 1800]\fR +.RS 8 +.TP 8 +\fBusage\fR +NTP failover timeout in seconds: time between PTP slave going into +LISTENING state, and failing over to NTP. 0 = fail over immediately. +This setting controls the time provider election hold time. +.TP 8 +\fBdefault\fR +\fI60\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:prefer_ntp [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Prefer NTP time synchronisation. Only use PTP when NTP not available. +Could be used when NTP runs with a local GPS receiver or another hardware reference. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:panic_mode_ntp [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Deprecated as of 2.3.1, but still supported: see \fIptppengine:panic_mode_release_clock\fR. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:sigusr2_clears_counters [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Clear counters after dumping all counter values on SIGUSR2. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:timing_acl_permit [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Permit access control list for timing and signaling messages. Format is a series of +network prefixes and/or IP addresses separated by commas, spaces, tabs or semicolons. +Accepted format is CIDR notation (a.b.c.d/mm), single IP address (a.b.c.d), +or full network/mask (a.b.c.d/m.m.m.m). Shortcuts can be used: 172.16/12 +is expanded to 172.16.0.0/12; 192.168/255.255 is expanded to +192.168.0.0/255.255.0.0, etc. The match is performed +on the source IP address of the incoming messages. IP access lists are +only supported when using the IP transport. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:timing_acl_deny [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Deny access control list for timing and signaling messages. Format is a series of +network prefixes and/or IP addresses separated by commas, spaces, tabs or semicolons. +Accepted format is CIDR notation (a.b.c.d/mm), single IP address (a.b.c.d), +or full network/mask (a.b.c.d/m.m.m.m). Shortcuts can be used: 172.16/12 +is expanded to 172.16.0.0/12; 192.168/255.255 is expanded to +192.168.0.0/255.255.0.0, etc. The match is performed +on the source IP address of the incoming messages. IP access lists are +only supported when using the IP transport. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:management_acl_permit [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Permit access control list for management messages. Format is a series of +network prefixes and/or IP addresses separated by commas, spaces, tabs or semicolons. +Accepted format is CIDR notation (a.b.c.d/mm), single IP address (a.b.c.d), +or full network/mask (a.b.c.d/m.m.m.m). Shortcuts can be used: 172.16/12 +is expanded to 172.16.0.0/12; 192.168/255.255 is expanded to +192.168.0.0/255.255.0.0, etc. The match is performed +on the source IP address of the incoming messages. IP access lists are +only supported when using the IP transport. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:management_acl_deny [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Deny access control list for management messages. Format is a series of +network prefixes and/or IP addresses separated by commas, spaces, tabs or semicolons. +Accepted format is CIDR notation (a.b.c.d/mm), single IP address (a.b.c.d), +or full network/mask (a.b.c.d/m.m.m.m). Shortcuts can be used: 172.16/12 +is expanded to 172.16.0.0/12; 192.168/255.255 is expanded to +192.168.0.0/255.255.0.0, etc. The match is performed +on the source IP address of the incoming messages. IP access lists are +only supported when using the IP transport. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:timing_acl_order [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIpermit-deny deny-permit \fR +.TP 8 +\fBusage\fR +Order in which permit and deny access lists are evaluated for timing +and signaling messages, the evaluation process is the same as for Apache httpd. \fBSee: +\fIhttp://httpd.apache.org/docs/current/mod/mod_access_compat.html#order\fR +.TP 8 +\fBdefault\fR +\fIdeny-permit\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBptpengine:management_acl_order [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIpermit-deny deny-permit \fR +.TP 8 +\fBusage\fR +Order in which permit and deny access lists are evaluated for management +messages, the evaluation process is the same as for Apache httpd. \fBSee: +\fIhttp://httpd.apache.org/docs/current/mod/mod_access_compat.html#order\fR +.TP 8 +\fBdefault\fR +\fIdeny-permit\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:no_adjust [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Do not adjust the clock. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:no_reset [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Do not reset the clock - only slew. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:step_startup_force [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Force clock step on first sync after startup regardless of offset and +\fIclock:no_reset\fR +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:step_startup [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Step clock on startup only if offset >= 1 second, ignoring +panic mode and \fIclock:no_reset\fR +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:set_rtc_on_step [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Attempt setting the RTC when stepping clock (Linux only - FreeBSD does +this for us. \fBWARNING:\fR this will always set the RTC to OS clock time, +regardless of time zones, so this assumes that RTC runs in UTC or otherwise +in the same timescale as PTP. True at least on most single-boot x86 Linux systems. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:drift_handling [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIreset preserve file \fR +.TP 8 +\fBusage\fR +Observed drift handling method between servo restarts: +.RS 12 +.TP 12 +\fIreset\fR +set to zero (not recommended) +.TP 12 +\fIpreserve\fR +use kernel value +.TP 12 +\fIfile\fR +load/save to drift file on startup/shutdown, use kernel value inbetween. +To specify drift file, use the \fBclock:drift_file\fR setting. +.RE +.TP 8 +\fBdefault\fR +\fIpreserve\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:drift_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify drift file +.TP 8 +\fBdefault\fR +\fI/etc/ptpd2_kernelclock.drift\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:leap_seconds_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify leap second file location (up to date version can be downloaded from: +http://www.ietf.org/timezones/data/leap-seconds.list). When configured, +PTP master will use data from this file to announce leap flags and UTC offset, +overriding OS information, and PTP slave will use data from this file as well as +information supplied by the GM. If configured, this file is always reloaded +on configuration reload (SIGHUP), reloaded on clock step and reloaded after +a leap second event to ensure the information is up to date. As of ptpd 2.3.1, +the file is bundled with ptpd and is installed into +(prefix)/share/ptpd/leap-seconds.list.ddMMMyyyy where ddMMMyyyy +is the leap seconds file expiry date. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:leap_second_pause_period [\fIINT\fB: 5 .. 600]\fR +.RS 8 +.TP 8 +\fBusage\fR +Time (seconds) before and after midnight that clock updates should be +suspended for during a leap second event. The total duration +of the pause is twice the configured duration. Clock updates are suspended +when there is a leap second event pending and time to midnight is less than +or equal to this value and resumed no earlier than this value after midnight. +Clock updates are resumed in a controlled manner - after a control message, +such as PTP announce. This ensures that the updated UTC offset is received +before any further clock updates. +.TP 8 +\fBdefault\fR +\fI5\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:leap_second_notice_period [\fIINT\fB: 3600 .. 86400]\fR +.RS 8 +.TP 8 +\fBusage\fR +Time (seconds) before midnight that PTPd starts announcing the leap second +if it's running as master. The IEEE 1588 standard suggests 12 hours notice +and this is the default, but it may be changed to allow more flexibility. +.TP 8 +\fBdefault\fR +\fI43200\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:leap_second_handling [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIaccept ignore step smear\fR +.TP 8 +\fBusage\fR +Clock sync behaviour during leap second events: +.RS 12 +.TP 12 +\fIaccept\fR +Inform OS kernel about the leap second and let the kernel insert or delete the leap second +.TP 12 +\fIignore\fR +Do not inform the kernel - this ends with a +/-1-second offset which is then slewed back down +.TP 12 +\fIstep\fR +Do not inform the kernel and step the clock immediately after the leap second event +.TP 12 +\fIsmear\fR +Gradually introduce an extra offset over a period of time before the leap second event, +which accumulates to +/-1 second (see \fIclock:leap_second_smear_period\fR). Once the clock +stabilises, this results in a clock frequency shift which is taken off after the event. +Once the leap second event is over, the extra offset is also removed and time is back in +line with master time. +.RE +.TP 8 +\fBdefault\fR +\fIaccept\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:leap_second_smear_period [\fIINT\fB: 3600 .. 86400]\fR +.RS 8 +.TP 8 +\fBusage\fR +When \fIclock:leap_second_handling\fR is set to \fIsmear\fR, this setting +defines the period (in seconds) before the leap second event, over which +the leap second offset is gradually added. Example: when set to 24 hours (86400), +an extra +/-11.5 microseconds is added every second (11.5 ppm clock frequency +offset). +.TP 8 +\fBdefault\fR +\fI86400\fR + + +.RE +.RE +.RS 0 +.TP 8 +\fBclock:max_offset_ppm [\fIINT\fB: 500 .. 1000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum absolute frequency shift which can be applied to the clock servo + when slewing the clock. Expressed in parts per million (1 ppm = shift of + 1 us per second. Values above 512 will use the tick duration correction + to allow even faster slewing. Default maximum is 512 without using tick. +.TP 8 +\fBdefault\fR +\fI500\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:delayfilter_stiffness [\fIINT\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Mean Path Delay filter stiffness. +.TP 8 +\fBdefault\fR +\fI6\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:kp [\fIFLOAT\fB: min: 0.000001 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Clock servo PI controller proportional component gain (kP). +.TP 8 +\fBdefault\fR +\fI0.100000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:ki [\fIFLOAT\fB: min: 0.000001 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Clock servo PI controller integral component gain (kI). +.TP 8 +\fBdefault\fR +\fI0.001000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:dt_method [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fInone constant measured \fR +.TP 8 +\fBusage\fR +How servo update interval (delta t) is calculated: +.RS 12 +.TP 12 +\fInone\fR +servo not corrected for update interval (dt always 1), +.TP 12 +\fIconstant\fR +constant value (target servo update rate) - sync interval for PTP, +.TP 12 +\fImeasured\fR +servo measures how often it's updated and uses this interval. +.RE +.TP 8 +\fBdefault\fR +\fIconstant\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:dt_max [\fIFLOAT\fB: 1.500000 .. 100.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum servo update interval (delta t) when using measured servo update interval +(\fIservo:dt_method\fR = \fBmeasured\fR), specified as sync interval multiplier +.TP 8 +\fBdefault\fR +\fI5.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:stability_detection [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable clock synchronisation servo stability detection +(based on standard deviation of the observed drift value) - drift will be saved to drift file / cached when considered stable, +also clock stability status will be logged. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:stability_threshold [\fIFLOAT\fB: 1.000000 .. 10000.000000]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify the observed drift standard deviation threshold in parts per billion (ppb) - if stanard deviation is within the threshold, servo +is considered stable. +.TP 8 +\fBdefault\fR +\fI5.000000\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:stability_period [\fIINT\fB: 1 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify for how many statistics update intervals the observed drift +standard deviation has to stay within threshold to be considered stable. +.TP 8 +\fBdefault\fR +\fI3\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:stability_timeout [\fIINT\fB: 1 .. 60]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify after how many minutes without stabilisation servo is considered +unstable. Assists with logging servo stability information and +allows to preserve observed drift if servo cannot stabilise. +.TP 8 +\fBdefault\fR +\fI10\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:max_delay [\fIINT\fB: 0 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Do not accept master to slave delay (delayMS - from Sync message) or slave to master delay (delaySM - from Delay Response message) +if greater than this value (nanoseconds). 0 = not used. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:max_delay_max_rejected [\fIINT\fB: min: 0 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum number of consecutive rejected delay measurements exceeding the maxDelay threshold (\fIservo:max_delay\fR), +before slave is reset. 0 = not checked. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:max_delay_stable_only [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +If \fBservo:max_delay\fR is set, perform the check only if clock servo has stabilised. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBservo:max_offset [\fIINT\fB: 0 .. 999999999]\fR +.RS 8 +.TP 8 +\fBusage\fR +Do not reset the clock if offset from master is greater than this value (nanoseconds). 0 = not used. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:config_templates [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Comma, space or tab-separated list of template names to be applied to the configuration +(see \fICONFIGURATION TEMPLATES AND TEMPLATE FILES\fR section). Templates are applied in the order +they are specified, so any overlapping settings from one template are overridden with settings +from the following template(s). PTPd provides some built-in templates - see the templates section +above; to see the built-in templates, run ptpd with \fI-T\fR or \fI--show-templates\fR. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:template_files [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Comma, space or tab-separated list of template file paths to be loaded +(see \fICONFIGURATION TEMPLATES AND TEMPLATE FILES\fR section). Template +files are also loaded in the order they are provided, so templates in one file +can be extended by templates in the next file(s); any overlapping settings are overridden +by following files. PTPd will not exit when one or more template files cannot be opened. +PTPd will always try to load \fI@prefix@/share/@PACKAGE_NAME@/templates.conf\fR on startup. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:enable_alarms [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable support for alarm and event notifications (see \fBALARMS\fR section). Alarms +enable self-diagnosing of common error conditions and events such as master change +or time properties change. When SNMP support is enabled (\fBglobal:enable_snmp\fR) +and SNMP trap support is enabled (\fBglobal:enable_snmp_traps\fR), alarms trigger +SNMP traps. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:alarm_timeout [\fIINT\fB: 0 .. 3600]\fR +.RS 8 +.TP 8 +\fBusage\fR +Mininmum alarm age (seconds) - minimal time between alarm set and clear notifications. +The condition can clear while alarm lasts, but notification (log or SNMP) will only +be triggered after the timeout. This option prevents from alarms flapping (repeated +set and clear notifications). +.TP 8 +\fBdefault\fR +\fI30\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:alarm_initial_delay [\fIINT\fB: 0 .. 3600]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay the start of alarm processing (seconds) after ptpd startup. This option +allows to avoid unnecessary alarms before PTPd starts synchronising, which should happen +after a few seconds, but could take longer in cases where multicast has to converge upstream, +or when there is a mismatch in message intervals and unicast signaling has to negotiate them down +(or up) to acceptable values. This also prevents from alerting on offset from master too soon +after startup (see \fBptpengine:offset_alarm_threshold\fR) - delay can be increased to cover +the initial sync period, however this is not recommended as an offset alarm after startup +can indicate a slave cold start. +.TP 8 +\fBdefault\fR +\fI10\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:enable_snmp [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable SNMP agent (if compiled with PTPD_SNMP). +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:enable_snmp_traps [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable reporting of alarms and events as SNMP traps. Requires PTPd to be compiled with PTPD_SNMP, +and requires alarms to be enabled (\fBglobal:enable_alarms\fR) +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:use_syslog [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Send log messages to syslog. Disabling this sends all messages to stdout (or speficied log file). +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:lock_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Lock file location +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:auto_lockfile [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Use mode specific and interface specific lock file (overrides \fBglobal:lock_file\fR). +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:lock_directory [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Lock file directory: used with automatic mode-specific lock files, +also used when no lock file is specified. When lock file +is specified, it's expected to be an absolute path. +.TP 8 +\fBdefault\fR +\fI/var/run\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:ignore_lock [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Skip lock file checking and locking. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:quality_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +File used to record data about sync packets. Enables recording when set. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:quality_file_max_size [\fIINT\fB: min: 0 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum sync packet record file size (in kB) - file will be truncated +if size exceeds the limit. 0 - no limit. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:quality_file_max_files [\fIINT\fB: 0 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable log rotation of the sync packet record file up to n files. +0 - do not rotate. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:quality_file_truncate [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Truncate the sync packet record file every time it is (re) opened: +startup and SIGHUP. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:status_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +File used to log ptpd2 status information. +.TP 8 +\fBdefault\fR +\fI/var/run/ptpd2.status\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_status [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable / disable writing status information to file. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:status_update_interval [\fIINT\fB: 1 .. 30]\fR +.RS 8 +.TP 8 +\fBusage\fR +Status file update interval in seconds. +.TP 8 +\fBdefault\fR +\fI1\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify log file path (event log). Setting this enables logging to file. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_file_max_size [\fIINT\fB: min: 0 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum log file size (in kB) - log file will be truncated if size exceeds +the limit. 0 - no limit. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_file_max_files [\fIINT\fB: 0 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable log rotation of the sync packet record file up to n files. +0 - do not rotate. + +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_file_truncate [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Truncate the log file every time it is (re) opened: startup and SIGHUP. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_level [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fILOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_ALL \fR +.TP 8 +\fBusage\fR +Specify log level (only messages at this priority or higer will be logged). +The minimal level is LOG_ERR. LOG_ALL enables debug output if compiled with +RUNTIME_DEBUG. +.TP 8 +\fBdefault\fR +\fILOG_ALL\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_file [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Specify statistics log file path. Setting this enables logging of +statistics, but can be overriden with \fBglobal:log_statistics\fR. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_log_interval [\fIINT\fB: min: 0 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Log timing statistics every n seconds for Sync and Delay messages (0 - log all). +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_file_max_size [\fIINT\fB: min: 0 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Maximum statistics log file size (in kB) - log file will be truncated if size exceeds the limit. 0 - no limit. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_file_max_files [\fIINT\fB: 0 .. 100]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable log rotation of the statistics file up to n files. 0 - do not rotate. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_file_truncate [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Truncate the statistics file every time it is (re) opened: startup and SIGHUP. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:dump_packets [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Dump the contents of every PTP packet. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:verbose_foreground [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Run in foreground with statistics and all messages logged to stdout. Overrides log file and statistics file settings and disables syslog. + +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:foreground [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Run in foreground - ignored when global:verbose_foreground is set. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:log_statistics [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Log timing statistics for every PTP packet received. Output is in CSV format and field headers +are always printed when starting or refreshing the statistics log. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_timestamp_format [\fISELECT\fB]\fR +.RS 8 +.TP 8 +\fBoptions\fR +\fIdatetime unix both \fR +.TP 8 +\fBusage\fR +Timestamp format used when logging timing statistics (when \fBglobal:log_statistics\fR is enabled): +.RS 12 +.TP 12 +\fIdatetime\fR +Formatted date and time: \fBYYYY-MM-DD hh:mm:ss.uuuuuu\fR +.TP 12 +\fIunix\fR +Unix timestamp with nanoseconds: \fBs.ns\fR +.TP 12 +\fIboth\fR +Formatted date and time followed by unix timestamp (adds one extra field to the log) +.RE +.TP 8 +\fBdefault\fR +\fIdatetime\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:periodic_updates [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Log a status update every time statistics are updated (\fIglobal:statistics_update_interval\fR). +This update is written to the main log target. Status updates are logged even if ptpd is configured +without support for statistics. +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:cpuaffinity_cpucore [\fIINT\fB: -1 .. 255]\fR +.RS 8 +.TP 8 +\fBusage\fR +Bind ptpd2 process to a selected CPU core number. 0 = first CPU core, etc. -1 = do not bind to a single core. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:statistics_update_interval [\fIINT\fB: 1 .. 60]\fR +.RS 8 +.TP 8 +\fBusage\fR +Clock synchronisation statistics update interval in seconds. Also controls how often periodic status information +is logged (when using \fIglobal:statistics_update_interval\fR). +.TP 8 +\fBdefault\fR +\fI30\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBglobal:timingdomain_election_delay [\fIINT\fB: 0 .. 3600 ]\fR +.RS 8 +.TP 8 +\fBusage\fR +Delay (seconds) before releasing a time service (NTP or PTP) +and electing a new one to control a clock. 0 = elect immediately +.TP 8 +\fBdefault\fR +\fI15\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBntpengine:enabled [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable NTPd integration +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBntpengine:control_enabled [\fIBOOLEAN\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +Enable control over local NTPd daemon +.TP 8 +\fBdefault\fR +\fIN\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBntpengine:check_interval [\fIINT\fB: 5 .. 600]\fR +.RS 8 +.TP 8 +\fBusage\fR +NTP control check interval in seconds +.TP 8 +\fBdefault\fR +\fI15\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBntpengine:key_id [\fIINT\fB: 0 .. 65535]\fR +.RS 8 +.TP 8 +\fBusage\fR +NTP key number - must be configured as a trusted control key in ntp.conf, +and be non-zero for the ntpengine:control_enabled setting to take effect. +.TP 8 +\fBdefault\fR +\fI0\fR + +.RE +.RE +.RS 0 +.TP 8 +\fBntpengine:key [\fISTRING\fB]\fR +.RS 8 +.TP 8 +\fBusage\fR +NTP key (plain text, max. 20 characters) - must match the key configured in +ntpd's keys file, and must be non-zero for the ntpengine:control_enabled +setting to take effect. +.TP 8 +\fBdefault\fR +\fI[none]\fR + +.RE +.RE + +.SH BUGS +Configuration file support has only been introduced in version 2.3. There may still be some inconsistencies +in the way some settings are parsed and while order should not make any difference, for some complex behaviours +it may still be the case. + +Please report any bugs using the bug tracker on the SourceForge page: +http://sourceforge.net/projects/ptpd/ + +.SH SEE ALSO +.xR ptpd2 8 +ptpd2(8) + +.SH AUTHORS +.PP +Steven Kreuzer +.PP +Gael Mace +.PP +George Neville-Neil +.PP +Wojciech Owczarek +.PP +Alexandre Van Kempen + +\fBptpd2.conf(5)\fR man page first written by Wojciech Owczarek for ptpd 2.3.0 in November 2013 diff --git a/src/ptpd/src/ptpd2.conf.default-full b/src/ptpd/src/ptpd2.conf.default-full new file mode 100644 index 0000000..2ceb573 --- /dev/null +++ b/src/ptpd/src/ptpd2.conf.default-full @@ -0,0 +1,841 @@ +; ======================================== +; PTPDv2 version 2.3.2 default configuration +; ======================================== + +; NOTE: the following settings are affected by ptpengine:preset selection: +; ptpengine:slave_only +; clock:no_adjust +; ptpengine:clock_class - allowed range and default value +; To see all preset settings, run ptpd2 -H (--long-help) + +; Network interface to use - eth0, igb0 etc. (required). +ptpengine:interface = + +; Backup network interface to use - eth0, igb0 etc. When no GM available, +; slave will keep alternating between primary and secondary until a GM is found. +; +ptpengine:backup_interface = + +; PTP engine preset: +; none = Defaults, no clock class restrictions +; masteronly = Master, passive when not best master (clock class 0..127) +; masterslave = Full IEEE 1588 implementation: +; Master, slave when not best master +; (clock class 128..254) +; slaveonly = Slave only (clock class 255 only) +; +; Options: none masteronly masterslave slaveonly +ptpengine:preset = slaveonly + +; Transport type for PTP packets. Ethernet transport requires libpcap support. +; Options: ipv4 ethernet +ptpengine:transport = ipv4 + +; Enable TransportSpecific field compatibility with 802.1AS / AVB (requires Ethernet transport) +ptpengine:dot1as = N + +; Disable PTP port. Causes the PTP state machine to stay in PTP_DISABLED state indefinitely, +; until it is re-enabled via configuration change or ENABLE_PORT management message. +ptpengine:disabled = N + +; IP transmission mode (requires IP transport) - hybrid mode uses +; multicast for sync and announce, and unicast for delay request and +; response; unicast mode uses unicast for all transmission. +; When unicast mode is selected, destination IP(s) may need to be configured +; (ptpengine:unicast_destinations). +; Options: multicast unicast hybrid +ptpengine:ip_mode = multicast + +; Enable unicast negotiation support using signaling messages +; +ptpengine:unicast_negotiation = N + +; When using unicast negotiation (slave), accept PTP messages from any master. +; By default, only messages from acceptable masters (ptpengine:unicast_destinations) +; are accepted, and only if transmission was granted by the master +; +ptpengine:unicast_any_master = N + +; PTP port number wildcard mask applied onto port identities when running +; unicast negotiation: allows multiple port identities to be accepted as one. +; This option can be used as a workaround where a node sends signaling messages and +; timing messages with different port identities +ptpengine:unicast_port_mask = 0 + +; Disable Best Master Clock Algorithm for unicast masters: +; Only effective for masteronly preset - all Announce messages +; will be ignored and clock will transition directly into MASTER state. +; +ptpengine:disable_bmca = N + +; When unicast negotiation enabled on a master clock, +; reply to transmission requests also in LISTENING state. +ptpengine:unicast_negotiation_listening = N + +; Use libpcap for sending and receiving traffic (automatically enabled +; in Ethernet mode). +ptpengine:use_libpcap = N + +; Disable UDP checksum validation on UDP sockets (Linux only). +; Workaround for situations where a node (like Transparent Clock). +; does not rewrite checksums +; +ptpengine:disable_udp_checksums = Y + +; Delay detection mode used - use DELAY_DISABLED for syntonisation only +; (no full synchronisation). +; Options: E2E P2P DELAY_DISABLED +ptpengine:delay_mechanism = E2E + +; PTP domain number. +ptpengine:domain = 0 + +; PTP port number (part of PTP Port Identity - not UDP port). +; For ordinary clocks (single port), the default should be used, +; but when running multiple instances to simulate a boundary clock, +; The port number can be changed. +ptpengine:port_number = 1 + +; Port description (returned in the userDescription field of PORT_DESCRIPTION management message and USER_DESCRIPTION management message) - maximum 64 characters +ptpengine:port_description = ptpd + +; Usability extension: if enabled, a slave-only clock will accept +; masters from any domain, while preferring the configured domain, +; and preferring lower domain number. +; NOTE: this behaviour is not part of the standard. +ptpengine:any_domain = N + +; Slave only mode (sets clock class to 255, overriding value from preset). +ptpengine:slave_only = Y + +; Specify latency correction (nanoseconds) for incoming packets. +ptpengine:inbound_latency = 0 + +; Specify latency correction (nanoseconds) for outgoing packets. +ptpengine:outbound_latency = 0 + +; Apply an arbitrary shift (nanoseconds) to offset from master when +; in slave state. Value can be positive or negative - useful for +; correcting for antenna latencies, delay assymetry +; and IP stack latencies. This will not be visible in the offset +; from master value - only in the resulting clock correction. +ptpengine:offset_shift = 0 + +; Compatibility option: In slave state, always respect UTC offset +; announced by best master, even if the the +; currrentUtcOffsetValid flag is announced FALSE. +; NOTE: this behaviour is not part of the standard. +ptpengine:always_respect_utc_offset = Y + +; Compatibility extension to BMC algorithm: when enabled, +; BMC for both master and save clocks will prefer masters +; nannouncing currrentUtcOffsetValid as TRUE. +; NOTE: this behaviour is not part of the standard. +ptpengine:prefer_utc_offset_valid = N + +; Compatibility option: when enabled, ptpd2 will ignore +; Announce messages from masters announcing currentUtcOffsetValid +; as FALSE. +; NOTE: this behaviour is not part of the standard. +ptpengine:require_utc_offset_valid = N + +; Time (seconds) unicast messages are requested for by slaves +; when using unicast negotiation, and maximum time unicast message +; transmission is granted to slaves by masters +; +ptpengine:unicast_grant_duration = 300 + +; PTP announce message interval in master state. When using unicast negotiation, for +; slaves this is the minimum interval requested, and for masters +; this is the only interval granted. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_announce_interval = 1 + +; Maximum Announce message interval requested by slaves when using unicast negotiation, +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_announce_interval_max = 5 + +; PTP announce receipt timeout announced in master state. +ptpengine:announce_receipt_timeout = 6 + +; PTP announce receipt timeout grace period in slave state: +; when announce receipt timeout occurs, disqualify current best GM, +; then wait n times announce receipt timeout before resetting. +; Allows for a seamless GM failover when standby GMs are slow +; to react. When set to 0, this option is not used. +ptpengine:announce_receipt_grace_period = 0 + +; PTP sync message interval in master state. When using unicast negotiation, for +; slaves this is the minimum interval requested, and for masters +; this is the only interval granted. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_sync_interval = 0 + +; Maximum Sync message interval requested by slaves when using unicast negotiation, +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_sync_interval_max = 5 + +; Override the Delay Request interval announced by best master. +ptpengine:log_delayreq_override = N + +; Automatically override the Delay Request interval +; if the announced value is 127 (0X7F), such as in +; unicast messages (unless using unicast negotiation) +ptpengine:log_delayreq_auto = Y + +; Delay request interval used before receiving first delay response +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_delayreq_interval_initial = 0 + +; Minimum delay request interval announced when in master state, +; in slave state overrides the master interval, +; required in hybrid mode. When using unicast negotiation, for +; slaves this is the minimum interval requested, and for masters +; this is the minimum interval granted. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_delayreq_interval = 0 + +; Maximum Delay Response interval requested by slaves when using unicast negotiation, +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_delayreq_interval_max = 5 + +; Minimum peer delay request message interval in peer to peer delay mode. +; When using unicast negotiation, this is the minimum interval requested, +; and the only interval granted. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_peer_delayreq_interval = 1 + +; Maximum Peer Delay Response interval requested by slaves when using unicast negotiation, +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_peer_delayreq_interval_max = 5 + +; Foreign master record size (Maximum number of foreign masters). +ptpengine:foreignrecord_capacity = 5 + +; Specify Allan variance announced in master state. +ptpengine:ptp_allan_variance = 65535 + +; Clock accuracy range announced in master state. +; Options: ACC_25NS ACC_100NS ACC_250NS ACC_1US ACC_2_5US ACC_10US ACC_25US ACC_100US ACC_250US ACC_1MS ACC_2_5MS ACC_10MS ACC_25MS ACC_100MS ACC_250MS ACC_1S ACC_10S ACC_10SPLUS ACC_UNKNOWN +ptpengine:ptp_clock_accuracy = ACC_UNKNOWN + +; Underlying time source UTC offset announced in master state. +ptpengine:utc_offset = 0 + +; Underlying time source UTC offset validity announced in master state. +ptpengine:utc_offset_valid = N + +; Underlying time source time traceability announced in master state. +ptpengine:time_traceable = N + +; Underlying time source frequency traceability announced in master state. +ptpengine:frequency_traceable = N + +; Time scale announced in master state (with ARB, UTC properties +; are ignored by slaves). When clock class is set to 13 (application +; specific), this value is ignored and ARB is used. +; Options: PTP ARB +ptpengine:ptp_timescale = PTP + +; Time source announced in master state. +; Options: ATOMIC_CLOCK GPS TERRESTRIAL_RADIO PTP NTP HAND_SET OTHER INTERNAL_OSCILLATOR +ptpengine:ptp_timesource = INTERNAL_OSCILLATOR + +; Clock class - announced in master state. Always 255 for slave-only. +; Minimum, maximum and default values are controlled by presets. +; If set to 13 (application specific time source), announced +; time scale is always set to ARB. This setting controls the +; states a PTP port can be in. If below 128, port will only +; be in MASTER or PASSIVE states (master only). If above 127, +; port will be in MASTER or SLAVE states. +ptpengine:clock_class = 255 + +; Priority 1 announced in master state,used for Best Master +; Clock selection. +ptpengine:priority1 = 128 + +; Priority 2 announced in master state, used for Best Master +; Clock selection. +ptpengine:priority2 = 128 + +; Number of consecutive resets to LISTENING before full network reset +; +ptpengine:max_listen = 5 + +; Specify unicast slave addresses for unicast master operation, or unicast +; master addresses for slave operation. Format is similar to an ACL: comma, +; tab or space-separated IPv4 unicast addresses, one or more. For a slave, +; when unicast negotiation is used, setting this is mandatory. +ptpengine:unicast_destinations = + +; Specify PTP domain number for each configured unicast destination (ptpengine:unicast_destinations). +; This is only used by slave-only clocks using unicast destinations to allow for each master +; to be in a separate domain, such as with Telecom Profile. The number of entries should match the number +; of unicast destinations, otherwise unconfigured domains or domains set to 0 are set to domain configured in +; ptpengine:domain. The format is a comma, tab or space-separated list of 8-bit unsigned integers (0 .. 255) +ptpengine:unicast_domains = + +; Specify a local preference for each configured unicast destination (ptpengine:unicast_destinations). +; This is only used by slave-only clocks using unicast destinations to allow for each master's +; BMC selection to be influenced by the slave, such as with Telecom Profile. The number of entries should match the number +; of unicast destinations, otherwise unconfigured preference is set to 0 (highest). +; The format is a comma, tab or space-separated list of 8-bit unsigned integers (0 .. 255) +ptpengine:unicast_local_preference = + +; Specify peer unicast adress for P2P unicast. Mandatory when +; running unicast mode and P2P delay mode. +ptpengine:unicast_peer_destination = + +; Enable handling of PTP management messages. +ptpengine:management_enable = Y + +; Accept SET and COMMAND management messages. +ptpengine:management_set_enable = N + +; Send explicit IGMP joins between engine resets and periodically +; in master state. +ptpengine:igmp_refresh = Y + +; Periodic IGMP join interval (seconds) in master state when running +; IPv4 multicast: when set below 10 or when ptpengine:igmp_refresh +; is disabled, this setting has no effect. +ptpengine:master_igmp_refresh_interval = 60 + +; Multicast time to live for multicast PTP packets (ignored and set to 1 +; for peer to peer messages). +ptpengine:multicast_ttl = 64 + +; DiffServ CodepPoint for packet prioritisation (decimal). When set to zero, +; this option is not used. Use 46 for Expedited Forwarding (0x2e). +ptpengine:ip_dscp = 0 + +; Enable statistical filter for Sync messages. +ptpengine:sync_stat_filter_enable = N + +; Type of filter used for Sync message filtering +; Options: none mean min max absmin absmax median +ptpengine:sync_stat_filter_type = min + +; Number of samples used for the Sync statistical filter +ptpengine:sync_stat_filter_window = 4 + +; Sample window type used for Sync message statistical filter. Delay Response outlier filter action. +; Sliding window is continuous, interval passes every n-th sample only. +; Options: sliding interval +ptpengine:sync_stat_filter_window_type = sliding + +; Enable statistical filter for Delay messages. +ptpengine:delay_stat_filter_enable = N + +; Type of filter used for Delay message statistical filter +; Options: none mean min max absmin absmax median +ptpengine:delay_stat_filter_type = min + +; Number of samples used for the Delay statistical filter +ptpengine:delay_stat_filter_window = 4 + +; Sample window type used for Delay message statistical filter +; Sliding window is continuous, interval passes every n-th sample only +; Options: sliding interval +ptpengine:delay_stat_filter_window_type = sliding + +; Enable outlier filter for the Delay Response component in slave state +ptpengine:delay_outlier_filter_enable = N + +; Delay Response outlier filter action. If set to 'filter', outliers are +; replaced with moving average. +; Options: discard filter +ptpengine:delay_outlier_filter_action = discard + +; Number of samples in the Delay Response outlier filter buffer +ptpengine:delay_outlier_filter_capacity = 20 + +; Delay Response outlier filter threshold (: multiplier for Peirce's maximum +; standard deviation. When set below 1.0, filter is tighter, when set above +; 1.0, filter is looser than standard Peirce's test. +; When autotune enabled, this is the starting threshold. +ptpengine:delay_outlier_filter_threshold = 1.000000 + +; Always run the Delay Response outlier filter, even if clock is being slewed at maximum rate +ptpengine:delay_outlier_filter_always_filter = N + +; Enable automatic threshold control for Delay Response outlier filter. +ptpengine:delay_outlier_filter_autotune_enable = Y + +; Delay Response outlier filter autotune low watermark - minimum percentage +; of discarded samples in the update period before filter is tightened +; by the autotune step value. +ptpengine:delay_outlier_filter_autotune_minpercent = 20 + +; Delay Response outlier filter autotune high watermark - maximum percentage +; of discarded samples in the update period before filter is loosened +; by the autotune step value. +ptpengine:delay_outlier_filter_autotune_maxpercent = 95 + +; The value the Delay Response outlier filter threshold is increased +; or decreased by when auto-tuning. +ptpengine:delay_outlier_autotune_step = 0.100000 + +; Minimum Delay Response filter threshold value used when auto-tuning +ptpengine:delay_outlier_filter_autotune_minthreshold = 0.100000 + +; Maximum Delay Response filter threshold value used when auto-tuning +ptpengine:delay_outlier_filter_autotune_maxthreshold = 5.000000 + +; Enable Delay filter step detection (delaySM) to block when certain level exceeded +ptpengine:delay_outlier_filter_stepdetect_enable = N + +; Delay Response step detection threshold. Step detection is performed +; only when delaySM is below this threshold (nanoseconds) +ptpengine:delay_outlier_filter_stepdetect_threshold = 1000000 + +; Delay Response step level. When step detection enabled and operational, +; delaySM above this level (nanosecond) is considered a clock step and updates are paused +ptpengine:delay_outlier_filter_stepdetect_level = 500000 + +; Initial credit (number of samples) the Delay step detection filter can block for +; When credit is exhausted, filter stops blocking. Credit is gradually restored +ptpengine:delay_outlier_filter_stepdetect_credit = 200 + +; Amount of credit for the Delay step detection filter restored every full sample window +ptpengine:delay_outlier_filter_stepdetect_credit_increment = 10 + +; Delay Response outlier weight: if an outlier is detected, determines +; the amount of its deviation from mean that is used to build the standard +; deviation statistics and influence further outlier detection. +; When set to 1.0, the outlier is used as is. +ptpengine:delay_outlier_weight = 1.000000 + +; Enable outlier filter for the Sync component in slave state. +ptpengine:sync_outlier_filter_enable = N + +; Sync outlier filter action. If set to 'filter', outliers are replaced +; with moving average. +; Options: discard filter +ptpengine:sync_outlier_filter_action = discard + +; Number of samples in the Sync outlier filter buffer. +ptpengine:sync_outlier_filter_capacity = 20 + +; Sync outlier filter threshold: multiplier for the Peirce's maximum standard +; deviation. When set below 1.0, filter is tighter, when set above 1.0, +; filter is looser than standard Peirce's test. +ptpengine:sync_outlier_filter_threshold = 1.000000 + +; Always run the Sync outlier filter, even if clock is being slewed at maximum rate +ptpengine:sync_outlier_filter_always_filter = N + +; Enable automatic threshold control for Sync outlier filter. +ptpengine:sync_outlier_filter_autotune_enable = Y + +; Sync outlier filter autotune low watermark - minimum percentage +; of discarded samples in the update period before filter is tightened +; by the autotune step value. +ptpengine:sync_outlier_filter_autotune_minpercent = 20 + +; Sync outlier filter autotune high watermark - maximum percentage +; of discarded samples in the update period before filter is loosened +; by the autotune step value. +ptpengine:sync_outlier_filter_autotune_maxpercent = 95 + +; Value the Sync outlier filter threshold is increased +; or decreased by when auto-tuning. +ptpengine:sync_outlier_autotune_step = 0.100000 + +; Minimum Sync outlier filter threshold value used when auto-tuning +ptpengine:sync_outlier_filter_autotune_minthreshold = 0.100000 + +; Maximum Sync outlier filter threshold value used when auto-tuning +ptpengine:sync_outlier_filter_autotune_maxthreshold = 5.000000 + +; Enable Sync filter step detection (delayMS) to block when certain level exceeded. +ptpengine:sync_outlier_filter_stepdetect_enable = N + +; Sync step detection threshold. Step detection is performed +; only when delayMS is below this threshold (nanoseconds) +ptpengine:sync_outlier_filter_stepdetect_threshold = 1000000 + +; Sync step level. When step detection enabled and operational, +; delayMS above this level (nanosecond) is considered a clock step and updates are paused +ptpengine:sync_outlier_filter_stepdetect_level = 500000 + +; Initial credit (number of samples) the Sync step detection filter can block for. +; When credit is exhausted, filter stops blocking. Credit is gradually restored +ptpengine:sync_outlier_filter_stepdetect_credit = 200 + +; Amount of credit for the Sync step detection filter restored every full sample window +ptpengine:sync_outlier_filter_stepdetect_credit_increment = 10 + +; Sync outlier weight: if an outlier is detected, this value determines the +; amount of its deviation from mean that is used to build the standard +; deviation statistics and influence further outlier detection. +; When set to 1.0, the outlier is used as is. +ptpengine:sync_outlier_weight = 1.000000 + +; Delay between moving to slave state and enabling clock updates (seconds). +; This allows one-way delay to stabilise before starting clock updates. +; Activated when going into slave state and during slave's GM failover. +; 0 - not used. +ptpengine:calibration_delay = 0 + +; PTP idle timeout: if PTPd is in SLAVE state and there have been no clock +; updates for this amout of time, PTPd releases clock control. +; +ptpengine:idle_timeout = 120 + +; Enable panic mode: when offset from master is above 1 second, stop updating +; the clock for a period of time and then step the clock if offset remains +; above 1 second. +ptpengine:panic_mode = N + +; Duration (minutes) of the panic mode period (no clock updates) when offset +; above 1 second detected. +ptpengine:panic_mode_duration = 2 + +; When entering panic mode, release clock control while panic mode lasts +; if ntpengine:* configured, this will fail over to NTP, +; if not set, PTP will hold clock control during panic mode. +ptpengine:panic_mode_release_clock = N + +; Do not exit panic mode until offset drops below this value (nanoseconds). +; 0 = not used. +ptpengine:panic_mode_exit_threshold = 0 + +; Use PTPd's process ID as the middle part of the PTP clock ID - useful for running multiple instances. +ptpengine:pid_as_clock_identity = N + +; Fail over to NTP when PTP time sync not available - requires +; ntpengine:enabled, but does not require the rest of NTP configuration: +; will warn instead of failing over if cannot control ntpd. +ptpengine:ntp_failover = N + +; NTP failover timeout in seconds: time between PTP slave going into +; LISTENING state, and releasing clock control. 0 = fail over immediately. +ptpengine:ntp_failover_timeout = 120 + +; Prefer NTP time synchronisation. Only use PTP when NTP not available, +; could be used when NTP runs with a local GPS receiver or another reference +ptpengine:prefer_ntp = N + +; Legacy option from 2.3.0: same as ptpengine:panic_mode_release_clock +ptpengine:panic_mode_ntp = N + +; Clear counters after dumping all counter values on SIGUSR2. +ptpengine:sigusr2_clears_counters = N + +; Permit access control list for timing packets. Format is a series of +; comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x, +; where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the +; subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the +; incoming messages. IP access lists are only supported when using the IP transport. +ptpengine:timing_acl_permit = + +; Deny access control list for timing packets. Format is a series of +; comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x, +; where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the +; subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the +; incoming messages. IP access lists are only supported when using the IP transport. +ptpengine:timing_acl_deny = + +; Permit access control list for management messages. Format is a series of +; comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x, +; where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the +; subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the +; incoming messages. IP access lists are only supported when using the IP transport. +ptpengine:management_acl_permit = + +; Deny access control list for management messages. Format is a series of +; comma, space or tab separated network prefixes: IPv4 addresses or full CIDR notation a.b.c.d/x, +; where a.b.c.d is the subnet and x is the decimal mask, or a.b.c.d/v.x.y.z where a.b.c.d is the +; subnet and v.x.y.z is the 4-octet mask. The match is performed on the source IP address of the +; incoming messages. IP access lists are only supported when using the IP transport. +ptpengine:management_acl_deny = + +; Order in which permit and deny access lists are evaluated for timing +; packets, the evaluation process is the same as for Apache httpd. +; Options: permit-deny deny-permit +ptpengine:timing_acl_order = deny-permit + +; Order in which permit and deny access lists are evaluated for management +; messages, the evaluation process is the same as for Apache httpd. +; Options: permit-deny deny-permit +ptpengine:management_acl_order = deny-permit + +; Do not adjust the clock +clock:no_adjust = N + +; Do not step the clock - only slew +clock:no_reset = N + +; Force clock step on first sync after startup regardless of offset and clock:no_reset +clock:step_startup_force = N + +; Step clock on startup if offset >= 1 second, ignoring +; panic mode and clock:no_reset +clock:step_startup = N + +; Attempt setting the RTC when stepping clock (Linux only - FreeBSD does +; this for us. WARNING: this will always set the RTC to OS clock time, +; regardless of time zones, so this assumes that RTC runs in UTC or +; at least in the same timescale as PTP. true at least on most +; single-boot x86 Linux systems. +clock:set_rtc_on_step = N + +; Observed drift handling method between servo restarts: +; reset: set to zero (not recommended) +; preserve: use kernel value, +; file: load/save to drift file on startup/shutdown, use kernel +; value inbetween. To specify drift file, use the clock:drift_file setting. +; Options: reset preserve file +clock:drift_handling = preserve + +; Specify drift file +clock:drift_file = /etc/ptpd2_kernelclock.drift + +; Time (seconds) before and after midnight that clock updates should pe suspended for +; during a leap second event. The total duration of the pause is twice +; the configured duration +clock:leap_second_pause_period = 5 + +; Time (seconds) before midnight that PTPd starts announcing the leap second +; if it's running as master +clock:leap_second_notice_period = 43200 + +; Specify leap second file location - up to date version can be downloaded from +; http://www.ietf.org/timezones/data/leap-seconds.list +clock:leap_seconds_file = + +; Behaviour during a leap second event: +; accept: inform the OS kernel of the event +; ignore: do nothing - ends up with a 1-second offset which is then slewed +; step: similar to ignore, but steps the clock immediately after the leap second event +; smear: do not inform kernel, gradually introduce the leap second before the event +; by modifying clock offset (see clock:leap_second_smear_period) +; Options: accept ignore step smear +clock:leap_second_handling = accept + +; Time period (Seconds) over which the leap second is introduced before the event. +; Example: when set to 86400 (24 hours), an extra 11.5 microseconds is added every second +clock:leap_second_smear_period = 86400 + +; Maximum absolute frequency shift which can be applied to the clock servo +; when slewing the clock. Expressed in parts per million (1 ppm = shift of +; 1 us per second. Values above 512 will use the tick duration correction +; to allow even faster slewing. Default maximum is 512 without using tick. +clock:max_offset_ppm = 500 + +; One-way delay filter stiffness. +servo:delayfilter_stiffness = 6 + +; Clock servo PI controller proportional component gain (kP). +servo:kp = 0.100000 + +; Clock servo PI controller integral component gain (kI). +servo:ki = 0.001000 + +; How servo update interval (delta t) is calculated: +; none: servo not corrected for update interval (dt always 1), +; constant: constant value (target servo update rate - sync interval for PTP, +; measured: servo measures how often it's updated and uses this interval. +; Options: none constant measured +servo:dt_method = constant + +; Maximum servo update interval (delta t) when using measured servo update interval +; (servo:dt_method = measured), specified as sync interval multiplier. +servo:dt_max = 5.000000 + +; Enable clock synchronisation servo stability detection +; (based on standard deviation of the observed drift value) +; - drift will be saved to drift file / cached when considered stable, +; also clock stability status will be logged. +servo:stability_detection = N + +; Specify the observed drift standard deviation threshold in parts per +; billion (ppb) - if stanard deviation is within the threshold, servo +; is considered stable. +servo:stability_threshold = 10.000000 + +; Specify for how many statistics update intervals the observed drift +; standard deviation has to stay within threshold to be considered stable. +servo:stability_period = 1 + +; Specify after how many minutes without stabilisation servo is considered +; unstable. Assists with logging servo stability information and +; allows to preserve observed drift if servo cannot stabilise. +; +servo:stability_timeout = 10 + +; Do accept master to slave delay (delayMS - from Sync message) or slave to master delay +; (delaySM - from Delay messages) if greater than this value (nanoseconds). 0 = not used. +servo:max_delay = 0 + +; Maximum number of consecutive delay measurements exceeding maxDelay threshold, +; before slave is reset. +servo:max_delay_max_rejected = 0 + +; If servo:max_delay is set, perform the check only if clock servo has stabilised. +; +servo:max_delay_stable_only = N + +; When enabled, Sync messages will only be accepted if sequence ID is increasing. This is limited to 50 dropped messages. +; +ptpengine:sync_sequence_checking = N + +; If set to non-zero, timeout in seconds, after which the slave resets if no clock updates made. +; +ptpengine:clock_update_timeout = 0 + +; Do not reset the clock if offset from master is greater +; than this value (nanoseconds). 0 = not used. +servo:max_offset = 0 + +; Enable SNMP agent (if compiled with PTPD_SNMP). +global:enable_snmp = N + +; Send log messages to syslog. Disabling this +; sends all messages to stdout (or speficied log file). +global:use_syslog = N + +; Lock file location +global:lock_file = + +; Use mode specific and interface specific lock file +; (overrides global:lock_file). +global:auto_lockfile = N + +; Lock file directory: used with automatic mode-specific lock files, +; also used when no lock file is specified. When lock file +; is specified, it's expected to be an absolute path. +global:lock_directory = /var/run + +; Skip lock file checking and locking. +global:ignore_lock = N + +; File used to record data about sync packets. Enables recording when set. +global:quality_file = + +; Maximum sync packet record file size (in kB) - file will be truncated +; if size exceeds the limit. 0 - no limit. +global:quality_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate. +; +global:quality_file_max_files = 0 + +; Truncate the sync packet record file every time it is (re) opened: +; startup and SIGHUP. +global:quality_file_truncate = N + +; File used to log ptpd2 status information. +global:status_file = /var/run/ptpd2.status + +; Enable / disable writing status information to file. +global:log_status = N + +; Status file update interval in seconds. +global:status_update_interval = 1 + +; Specify log file path (event log). Setting this enables logging to file. +global:log_file = + +; Maximum log file size (in kB) - log file will be truncated if size exceeds +; the limit. 0 - no limit. +global:log_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate. +; +global:log_file_max_files = 0 + +; Truncate the log file every time it is (re) opened: startup and SIGHUP. +global:log_file_truncate = N + +; Specify log level (only messages at this priority or higer will be logged). +; The minimal level is LOG_ERR. LOG_ALL enables debug output if compiled with +; RUNTIME_DEBUG. +; Options: LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_ALL +global:log_level = LOG_ALL + +; Specify statistics log file path. Setting this enables logging of +; statistics, but can be overriden with global:log_statistics. +global:statistics_file = + +; Log timing statistics every n seconds for Sync and Delay messages +; (0 - log all). +global:statistics_log_interval = 0 + +; Maximum statistics log file size (in kB) - log file will be truncated +; if size exceeds the limit. 0 - no limit. +global:statistics_file_max_size = 0 + +; Enable log rotation of the statistics file up to n files. 0 - do not rotate. +global:statistics_file_max_files = 0 + +; Truncate the statistics file every time it is (re) opened: startup and SIGHUP. +global:statistics_file_truncate = N + +; Dump the contents of every PTP packet +global:dump_packets = N + +; Run in foreground with statistics and all messages logged to stdout. +; Overrides log file and statistics file settings and disables syslog. +; +global:verbose_foreground = N + +; Run in foreground - ignored when global:verbose_foreground is set +global:foreground = N + +; Log timing statistics for every PTP packet received +; +global:log_statistics = N + +; Timestamp format used when logging timing statistics +; (when global:log_statistics is enabled): +; datetime - formatttted date and time: YYYY-MM-DD hh:mm:ss.uuuuuu +; unix - Unix timestamp with nanoseconds: s.ns +; both - Formatted date and time, followed by unix timestamp +; (adds one extra field to the log) +; +; Options: datetime unix both +global:statistics_timestamp_format = datetime + +; Bind ptpd2 process to a selected CPU core number. +; 0 = first CPU core, etc. -1 = do not bind to a single core. +global:cpuaffinity_cpucore = -1 + +; Clock synchronisation statistics update interval in seconds +; +global:statistics_update_interval = 30 + +; Log a status update every time statistics are updated (global:statistics_update_interval). +; The updates are logged even when ptpd is configured without statistics support +global:periodic_updates = N + +; Delay (seconds) before releasing a time service (NTP or PTP) and electing a new one to control a clock. 0 = elect immediately +; +global:timingdomain_election_delay = 15 + +; Enable NTPd integration +ntpengine:enabled = N + +; Enable control over local NTPd daemon +ntpengine:control_enabled = N + +; NTP control check interval in seconds +; +ntpengine:check_interval = 15 + +; NTP key number - must be configured as a trusted control key in ntp.conf, +; and be non-zero for the ntpengine:control_enabled setting to take effect. +; +ntpengine:key_id = 0 + +; NTP key (plain text, max. 20 characters) - must match the key configured in +; ntpd's keys file, and must be non-zero for the ntpengine:control_enabled +; setting to take effect. +; +ntpengine:key = + +; ========= newline required in the end ========== + diff --git a/src/ptpd/src/ptpd2.conf.minimal b/src/ptpd/src/ptpd2.conf.minimal new file mode 100644 index 0000000..d86b68d --- /dev/null +++ b/src/ptpd/src/ptpd2.conf.minimal @@ -0,0 +1,38 @@ +; ============================================================================== +; This is a minimal configuration for a PTPv2 slave +; For a full list of options run ptpd2 -H or see the documentation and man pages. +; +; NOTE: for best results, do read the ptpd2.conf(5) man page as many settings +; such as outlier filters and statistics filters can greatly improve +; the operation of PTPd. +; ============================================================================== + +; interface has to be specified +ptpengine:interface= + +; PTP domain +ptpengine:domain=0 + +; available presets are slaveonly, masteronly and masterslave (full IEEE 1588 implementation) +ptpengine:preset=slaveonly + +; multicast for both sync and delay requests - use hybrid for unicast delay requests +ptpengine:ip_mode=multicast + +; when enabled, sniffing is used instead of sockets to send and receive packets +ptpengine:use_libpcap=n + +; log file, event log only. if timing statistics are needed, see statistics_file +global:log_file=/var/log/ptpd2.log + +; status file providing an overview of ptpd's operation and statistics +global:log_status=y + +; required if ip_mode is set to hybrid +;ptpengine:log_delayreq_interval=0 + +; uncomment this to log a timing log like in previous ptpd versions +;global:statistics_file=/var/log/ptpd2.stats + +; always keep a new line in the end + diff --git a/src/ptpd/src/score/inc/Std_Types.h b/src/ptpd/src/score/inc/Std_Types.h new file mode 100644 index 0000000..b10418e --- /dev/null +++ b/src/ptpd/src/score/inc/Std_Types.h @@ -0,0 +1,39 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef STD_TYPES_H_ +#define STD_TYPES_H_ + +#ifdef __cplusplus + +#include + +// Type aliases so that the defined APIs are not altered from the specification +using uint8 = uint8_t; +using uint16 = uint16_t; +using uint32 = uint32_t; +using uint64 = uint64_t; +using boolean = bool; + +#define NULL_PTR nullptr + +#else // __cplusplus + +#include +#include + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef bool boolean; + +#define NULL_PTR ((void*)0) + +#endif // __cplusplus + +#define FALSE false +#define TRUE true + +#endif /* STD_TYPES_H_ */ diff --git a/src/ptpd/src/score/inc/_export.h b/src/ptpd/src/score/inc/_export.h new file mode 100644 index 0000000..f882616 --- /dev/null +++ b/src/ptpd/src/score/inc/_export.h @@ -0,0 +1,45 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_CRC_EXPORT_H +#define SCORE_CRC_EXPORT_H + +#ifdef SCORE_CRC_STATIC_DEFINE +# define SCORE_CRC_EXPORT +# define SCORE_CRC_NO_EXPORT +#else +# ifndef SCORE_CRC_EXPORT +# ifdef SCORE_CRC_lib_EXPORTS + /* We are building this library */ +# define SCORE_CRC_EXPORT __attribute__((visibility("default"))) +# else + /* We are using this library */ +# define SCORE_CRC_EXPORT __attribute__((visibility("default"))) +# endif +# endif + +# ifndef SCORE_CRC_NO_EXPORT +# define SCORE_CRC_NO_EXPORT __attribute__((visibility("hidden"))) +# endif +#endif + +#ifndef SCORE_CRC_DEPRECATED +# define SCORE_CRC_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef SCORE_CRC_DEPRECATED_EXPORT +# define SCORE_CRC_DEPRECATED_EXPORT SCORE_CRC_EXPORT SCORE_CRC_DEPRECATED +#endif + +#ifndef SCORE_CRC_DEPRECATED_NO_EXPORT +# define SCORE_CRC_DEPRECATED_NO_EXPORT SCORE_CRC_NO_EXPORT SCORE_CRC_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef SCORE_CRC_NO_DEPRECATED +# define SCORE_CRC_NO_DEPRECATED +# endif +#endif + +#endif /* SCORE_CRC_EXPORT_H */ diff --git a/src/ptpd/src/score/inc/score_ctc_helper.h b/src/ptpd/src/score/inc/score_ctc_helper.h new file mode 100644 index 0000000..db0d34c --- /dev/null +++ b/src/ptpd/src/score/inc/score_ctc_helper.h @@ -0,0 +1,20 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_CTC_HELPER_H_ +#define SCORE_CTC_HELPER_H_ + +#ifdef __CTC__ +#define EXCLUDE_COVERAGE_START(justification) _Pragma("CTC ANNOTATION justification") _Pragma("CTC SKIP") +#else +#define EXCLUDE_COVERAGE_START(justification) +#endif + +#ifdef __CTC__ +#define EXCLUDE_COVERAGE_END _Pragma("CTC ENDSKIP") +#else +#define EXCLUDE_COVERAGE_END +#endif + +#endif /* SCORE_CTC_HELPER_H_ */ \ No newline at end of file diff --git a/src/ptpd/src/score/inc/score_ptp_arith.h b/src/ptpd/src/score/inc/score_ptp_arith.h new file mode 100644 index 0000000..6d85914 --- /dev/null +++ b/src/ptpd/src/score/inc/score_ptp_arith.h @@ -0,0 +1,76 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_ARITH_H +#define SCORE_PTP_ARITH_H + +#include "score_ptp_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Algorithm to add two timestamps. + * @details + * This function enables the user to add two timestamps. The timestamps + * have sign as well. Hence as per the sign, the addition/subtraction shall be + * performed. + * @param[in] timeStampIn1 First Timestamp operand. + * @param[in] timeStampIn2 Second Timestamp operand. + * @param[out] timeStampResultOut The result is of the operation is + * updated here. + * @return None. + */ +void Score_PtpCalculateTimeSum(const Score_PtpSignedTimeStampType *timeStampIn1, + const Score_PtpSignedTimeStampType *timeStampIn2, + Score_PtpSignedTimeStampType *timeStampResultOut); + +/** @brief Algorithm to subtract two timestamps. + * @details + * This function enables the user to subtract two timestamps. + * @param[in] timeStampIn1 First Timestamp operand. + * @param[in] timeStampIn2 Second Timestamp operand. + * @param[out] timeStampResultOut The result is of the operation is + * updated here. + * @return None. + */ +void Score_PtpCalculateTimeDiff(const Score_PtpSignedTimeStampType *timeStampIn1, + const Score_PtpSignedTimeStampType *timeStampIn2, + Score_PtpSignedTimeStampType *timeStampResultOut); + +/** @brief Algorithm to convert uint64_t(nanoseconds) value to TimeStamp stamp. + * @details + * This function enables the user convert uint64_t value into TimeStamp type. + * It also initializes the sign of the TimeStamp generated as POSITIVE. + * @param[in] timeValue Time value in 64 bits format. + * @param[in] factor Number of bits to shift. + * @param[out] timeStampResultOut The result is of the operation is updated + * here. + * @return None. + */ +void Score_PtpConvertToTimeStamp(uint64_t timeValue, uint8_t factor, Score_PtpSignedTimeStampType *timeStampResultOut); + +/** @brief Algorithm to interpolate the global time based on VirtualLocalTimes. + * @details + * This function interpolates the global time based on the passed + * VirtualLocalTime. The computation is as follows : + * new_global_time = + * global_time_passed + (new_virtual_local_time - old_virtual_local_time) + * @param[in] vltCurrentIn The new Virtual Local time. + * @param[in] vltPreviousIn The previous/old Virtual Local Time. + * @param[in] globalTimeStampPreviousIn The previous/old global time. + * @param[out] resultCurrentTimeStampOut Pointer to newly computed global + * time. + * @return None. + */ +void Score_PtpInterpolateTime(const TSync_VirtualLocalTimeType *vltCurrentIn, + const TSync_VirtualLocalTimeType *vltPreviousIn, + const Score_PtpSignedTimeStampType *globalTimeStampPreviousIn, + Score_PtpSignedTimeStampType *resultCurrentTimeStampOut); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SCORE_PTP_ARITH_H */ diff --git a/src/ptpd/src/score/inc/score_ptp_common.h b/src/ptpd/src/score/inc/score_ptp_common.h new file mode 100644 index 0000000..e1ce599 --- /dev/null +++ b/src/ptpd/src/score/inc/score_ptp_common.h @@ -0,0 +1,135 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_COMMON_H +#define SCORE_PTP_COMMON_H + +#include +#include +#include "score_ptp_config.h" +#include "score_ptp_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Global configuration variable for PTP */ +extern Score_PtpConfigType ptpConfig; + +/** @brief Init function to update configuration information. + * @details + * This function stores the configuration information into a global structure. + * The information is received from the command line options. + * @param[in] pdelay Static Propagation delay. + * @param[in] domainNumber Time Base Identifier. + * @param[in] isProvider If the ptpd instance is Provider. + * @return Score_PtpReturnType SCORE_PTP_E_OK for successful initialization + * SCORE_PTP_E_NOT_OK otherwise. + */ +Score_PtpReturnType Score_PtpInitialize(double pdelay, uint16_t domainNumber, Score_PtpBooleanType isProvider); + +/** @brief Deinit function to perform graceful shutdown. + * @details + * This functions performs all the activities associated with graceful shutdown + * - Like close the connection with TSync, set the tsync handle to NULL. + * @param None. + * @return None. + */ +void Score_PtpDeinitialize(void); + +/** @brief Update the VirtualLocalTime for given message type. + * @details + * This functions gets the Virtual local Time from the TSync and updates it in + * the provider data structure. Dependending on the argument - Sync or FUP + * the VLT will be updated in the respective variable. These values are T0 and + * T2 for the preciseOriginTime computation. + * @param vltEgress SCORE_PTP_EGRESS_T0 for updating T0 + * SCORE_PTP_EGRESS_T4 for updating T4. + * @return None. + */ +void Score_PtpReadEgressVlt(Score_PtpEgressVltType vltEgress); + +/** @brief Update the GlobalTime and VirtualLocalTime for T0 and T0Vlt + * respectively. + * @details + * This function gets the Virtual local Time and GlobalTime from TSync and + * store it as T0 and T0Vlt. These values are used later to compute OriginTime. + * @param None. + * @return None. + */ +void Score_PtpBusGetGlobalTime(void); + +/** @brief Fetch T4Vlt and compute PreciseOriginTime . + * @details + * This functions fetches T4Vlt from tsync. Then based on T4Vlt and T0Vlt + * (which is stored previously in Provider information), T0 + * is interpolated to compute the PreciseOriginTime. + * @param None. + * @return None. + */ +void Score_PtpProviderComputeOriginTime(void); + +/** @brief Fetch PreciseOriginTime from Provider data-structure. + * @details + * This functions fetches origin time that was computed and stored in provider + * data-structure based on T4Vlt and T0Vlt + * @param originTimeOut Pointer to store the computed origin time. + * @return None. + */ +void Score_PtpWriteOriginTimeToBuffer(uint8_t *originTimeOut, uint8_t originTimeSize); + +/** @brief Callback function to indicate Immediate time transmission. + * @details + * This function is called by TSync to indicate that immediate time transmission + * is needed. It sets immediateTimeTransmission variable to TRUE. This will enable the time + * transmission on the Bus in the next cycle. + * @param[in] domainId The domain id for which time has to be + * transmitted. + * @return E_OK if successful, E_NOT_OK otherwise. + */ +TSync_ReturnType Score_PtpTransmitGlobalTimeCallback(TSync_SynchronizedTimeBaseType domainId); + +/** @brief Returns if Immediate time transmission is set to TRUE. + * @details + * This function returns if the Immediate time transmission is set to TRUE. + * @param[in] None + * @return TRUE if Immediate time transmission is TRUE, FALSE otherwise. + */ +Score_PtpBooleanType Score_PtpIsImmediateTimeTriggerTrue(void); + +/** @brief Resets the Immediate time transmission flag to FALSE. + * @details + * This function resets the Immediate time transmission flag to FALSE. It shall + * be called as soon as the transmission is initiated. + * @param[in] None. + * @return None. + */ +void Score_PtpResetImmediateTimeTrigger(void); + +/** @brief Configure function to read configuration from tsync-ptp-lib. + * @details + * This function retrieves the configuration data from tsync-ptp-lib during + * the startup. The configuation is stored into the global configuration + * structure. + * @param[in] isProvider If the ptpd instance is Provider. + * @return Score_PtpReturnType SCORE_PTP_E_OK for successful config update + * SCORE_PTP_E_NOT_OK otherwise. + */ +Score_PtpReturnType Score_PtpConfigure(Score_PtpBooleanType isProvider); + +/** @brief Stores the followup header information. + * @details + * This function reads the followup header information from the ptpd data structures(open source data structure) + * into the global structure used by SCORE extensions.The values in this header files are as received by the protocol + * expect that they are converted from big endian to host format. + * @return None. + */ +struct MsgHeaderTag; +void Score_PtpExtractFupHeaderInfo(const struct MsgHeaderTag *followupHeaderInfoIn, Score_PtpBooleanType isProvider); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SCORE_PTP_COMMON_H */ diff --git a/src/ptpd/src/score/inc/score_ptp_config.h b/src/ptpd/src/score/inc/score_ptp_config.h new file mode 100644 index 0000000..80b6b1a --- /dev/null +++ b/src/ptpd/src/score/inc/score_ptp_config.h @@ -0,0 +1,58 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_CONFIG_H +#define SCORE_PTP_CONFIG_H + +#include "score_ptp_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Type to store all the global variables that are dependent on configuration. + * The configuration is read from the tsync-ptp-lib. Depending on the configuration, each of the items below will be + * intialized once during intialization and used during the lifetime. + */ +typedef struct { + /** @brief The variable holding the length of the follow-up message. + * The length depends on the configuration parameter "MessageCompliance" */ + uint8_t followupLength; + + /** @brief The variable holding the length of all the SubTLVs configured */ + uint8_t configSubTlvLength; + + /** @brief The variable holds the bit masks for the configured time flags */ + uint8_t crcTimeFlags; +} Score_ConfigGlobals; + +/** @brief Type for storing the configuration information and information that + * is derived from the configuration. Configuration info mainly comes from the + * command line. + */ +typedef struct { + /** @brief TimeBase id associated with the current ptpd instance */ + uint16_t timebaseId; + + /** @brief The static GlobalPropogationDelay - this is statically configured + * as per autosar requirement. */ + /* Todo: This Pdelay value must be included in TSync_TimeBaseConfiguration + going forward - Perhaps when we support AUTOSAR 23-11 */ + Score_PtpSignedTimeStampType pdelay; + + /** @brief Time base handle assciated with the above TimeBase id */ + TSync_TimeBaseHandleType timebaseHandle; + + /** @brief This structure holds all the static configuration for the + * timebase */ + TSync_TimeBaseConfiguration timebaseConfig; + + Score_ConfigGlobals configGlobals; +} Score_PtpConfigType; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SCORE_PTP_CONFIG_H */ diff --git a/src/ptpd/src/score/inc/score_ptp_consumer.h b/src/ptpd/src/score/inc/score_ptp_consumer.h new file mode 100644 index 0000000..6246b6b --- /dev/null +++ b/src/ptpd/src/score/inc/score_ptp_consumer.h @@ -0,0 +1,63 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_CONSUMER_H +#define SCORE_PTP_CONSUMER_H + +#include +#include "score_ptp_types.h" +#include "score_ptp_arith.h" +#include "score_ptp_subtlv.h" +#include "score_ptp_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief The structure that is used to store the information for computation + * of globalTime */ +extern Score_PtpConsumerDataType consumerData; + +/** @brief Function to update the follow-up information received . + * @details + * This function stores the follow-up information into a global data structure. + * The information is originally received from the follow-up message. + * @param[in] originTimeIn Precise Origin Time. + * @param[in] corrTimeIn Correction field. + * @param[in] fupInfoIn Pointer to buffer containing fup info. + * @return None. + */ +void Score_PtpReadFupInfoFromBuffer(const uint8_t *originTimeIn, + const uint8_t *corrTimeIn, + const uint8_t *fupInfoIn); + +/** @brief Update the VirtualLocalTime for given message type. + * @details + * This functions gets the Virtual local Time from the TSync and updates it in + * the consumer data structure. Dependending on the argument - Sync or FUP + * the VLT will be updated in the respective variable. These values are T1 and + * T2 for the globalTime computation. + * @param vltIngress SCORE_PTP_INGRESS_T1 for updating T1 + * SCORE_PTP_INGRESS_T2 for updating T2. + * @return None. + */ +void Score_PtpReadIngressVlt(Score_PtpIngressVltType vltIngress); + +/** @brief Computes the GlobalTime and calls BusSetGlobalTime. + * @details + * This functions performs the globalTime computation and makes call to + * BusSetGlobalTime. The fomula for globalTime computation is: + * globalTime = PreciseOriginTime + correctionfield + (T2vlt-T1vlt) + Pdelay; + * localTime = T2vlt, + * measureData = Pdelay + * @param None. + * @return None. + */ +void Score_PtpBusSetGlobalTime(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SCORE_PTP_CONSUMER_H */ diff --git a/src/ptpd/src/score/inc/score_ptp_subtlv.h b/src/ptpd/src/score/inc/score_ptp_subtlv.h new file mode 100644 index 0000000..04df41a --- /dev/null +++ b/src/ptpd/src/score/inc/score_ptp_subtlv.h @@ -0,0 +1,40 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_SUBTLV_H +#define SCORE_PTP_SUBTLV_H + +#include + +#include "score_ptp_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Updates the Autosar TLV information into the ptpd buffer. + * @details + * This function is used by Provider to write the Autosar TLV information to + * outgoing buffer. + * @param[out] autosarTlvBufferOut Pointer to buffer where Autosar TLV can + * be updated. + * @return None. + */ +void Score_PtpWriteAutosarTlvToBuffer(uint8_t *autosarTlvBufferOut); + +/** @brief Reads the Autosar TLV information from the ptpd buffer. + * @details + * This function is used by Consumer to read/extract the TLV from the + * incoming buffer. + * @param[in] autosarTlvBufferIn Pointer to buffer where Autosar Tlv information + * can be read from. + * @return TRUE if Autosar SubTLVs were read successfully, FALSE otherwise + */ +Score_PtpBooleanType Score_PtpReadSubTlvFromBuffer(const uint8_t *autosarTlvBufferIn); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SCORE_PTP_SUBTLV_H */ diff --git a/src/ptpd/src/score/inc/score_ptp_types.h b/src/ptpd/src/score/inc/score_ptp_types.h new file mode 100644 index 0000000..f24201f --- /dev/null +++ b/src/ptpd/src/score/inc/score_ptp_types.h @@ -0,0 +1,412 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_TYPES_H +#define SCORE_PTP_TYPES_H + +#include +#include "tsync_ptp_lib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Macro to checks if a specific CRC flag in a variable is set. + */ +#define SCORE_PTP_CHECK_CRC_FLAG(variable, bitmask) ((variable & bitmask) == bitmask) + +/** @brief Macro to set a bit in CRC time flags, in the given variable + */ +#define SCORE_PTP_SET_CRC_FLAG(variable, bitmask) (variable |= bitmask) + +/** + * @brief Macro to define length of port number(to store PortIdentity) + */ +#define SCORE_PTP_PORT_NUMBER_LENGTH 2u + +/** + * @brief Macro to define length of clock identity(to store PortIdentity) + */ +#define SCORE_PTP_CLOCK_IDENTITY_LENGTH 8u + +/** + * @brief Macro to define length of port identity + */ +#define SCORE_PTP_FUP_MESSAGE_HEADER_PORT_IDENTITY_LENGTH (SCORE_PTP_CLOCK_IDENTITY_LENGTH + SCORE_PTP_PORT_NUMBER_LENGTH) + +/** + * @brief Macro to define length of precise origin time + */ +#define SCORE_PTP_FUP_MESSAGE_PRECISE_ORIGIN_TIME_LENGTH 10u + +/** + * @brief Macro to define length of domain number(timebase id) + */ +#define SCORE_PTP_FUP_MESSAGE_HEADER_DOMAIN_NUMBER_LENGTH 2u + +/** + * @brief Macro to define length of message length field + */ +#define SCORE_PTP_FUP_MESSAGE_HEADER_MESSAGE_LENGTH_BYTES 2u + +/** + * @brief Macro to define length of Correction field inside the followup header + */ +#define SCORE_PTP_FUP_MESSAGE_HEADER_CORRECTION_FIELD_LENGTH 8u + +/** + * @brief Macro to define length of Sequence ID inside the followup header + */ +#define SCORE_PTP_FUP_MESSAGE_HEADER_SEQUENCE_ID_LENGTH 2u + +/** + * @brief Macro to define indices of various fields in Followup message + */ +#define SCORE_PTP_FUP_MSG_TYPE_IDX 0u +#define SCORE_PTP_FUP_MSG_LENGTH_IDX 2u +#define SCORE_PTP_FUP_FLAGS_OCTET_0_IDX 6u +#define SCORE_PTP_FUP_FLAGS_OCTET_1_IDX 7u +#define SCORE_PTP_FUP_SEQUENCE_ID_IDX 30u +#define SCORE_PTP_FUP_CONTROL_IDX 32u +#define SCORE_PTP_FUP_LOG_MSG_INTERVAL_IDX 33u +#define SCORE_PTP_FUP_ORIGIN_TIME_SECONDSHI_IDX 34u +#define SCORE_PTP_FUP_ORIGIN_TIME_SECONDS_IDX 36u +#define SCORE_PTP_FUP_ORIGIN_TIME_NANOSECONDS_IDX 40u +#define SCORE_PTP_FUP_INFO_START_IDX 44u +#define SCORE_PTP_FUP_INFO_AUTOSAR_START_IDX 76u + +/** + * @brief Macros to define SubTlv type for each SubTLVs */ +#define SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC 0x61 +#define SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC 0x60 +#define SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED 0x28 +#define SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC 0x50 +#define SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITHOUT_CRC 0x51 + +/** + * @brief Subtlv length field for UserData - user bytes + crc */ +#define SCORE_PTP_FUP_SUBTLV_LEN_FIELD_USERDATA 5u + +/** + * @brief Subtlv length field for TimeSecured - CRC_Time_Flags + CRC_Time_0 + CRC_Time_1 */ +#define SCORE_PTP_FUP_SUBTLV_LEN_FIELD_TIMESECURED 3u + +/** + * @brief Macros to define SubTLV length in bytes for each SubTLV message + */ +#define SCORE_PTP_FUP_SUBTLV_TIMESECURED_LENGTH 5u +#define SCORE_PTP_FUP_SUBTLV_STATUS_LENGTH 4u +#define SCORE_PTP_FUP_SUBTLV_USERDATA_LENGTH 7u +#define SCORE_PTP_FUP_SUBTLV_OFS_LENGTH 19u +#define SCORE_PTP_TOTAL_SUBTLV_LENGTH \ + (SCORE_PTP_FUP_SUBTLV_TIMESECURED_LENGTH + SCORE_PTP_FUP_SUBTLV_STATUS_LENGTH + SCORE_PTP_FUP_SUBTLV_USERDATA_LENGTH + \ + SCORE_PTP_FUP_SUBTLV_OFS_LENGTH) +/** + * @brief Total Followup TLV information (IEEE + AUTOSAR ) = 79 bytes + * FollowUp Tlv info from IEEE = 32 bytes + * Followup Tlv info from AUOSAR = 10 bytes + * Followup SubTlv from AUTOSAR = 37 bytes + */ +#define SCORE_PTP_FUP_INFO_TLV_LENGTH 79u + +/** + * @brief Macros for Follow-Up information according to IEEE 802.1AS + */ +#define SCORE_PTP_FUP_TLV_TYPE 0x0003u +#define SCORE_PTP_FUP_TLV_LENGTH 0x001Cu /* 28u */ +#define SCORE_PTP_FUP_TLV_ORGAID 0x0080C2u +#define SCORE_PTP_FUP_TLV_ORGASUBTYPE 0x00001u +#define SCORE_PTP_FUP_TLV_SCALEDRATEOFF 0x00000000u +#define SCORE_PTP_FUP_TLV_GMTIMEBASEIND 0x0000u +#define SCORE_PTP_FUP_TLV_LASTGMPHASECHG 0x00u +#define SCORE_PTP_FUP_TLV_LASTGMFREQCHG 0x000000u + +/** @brief Macros for Follow-Up information according to AUTOSAR + */ +#define SCORE_PTP_FUP_AUTOSAR_TLV_TYPE 0x0003u +#define SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID 0x1A75FB /* AUTOSAR */ +#define SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE 0x605676 /* BCD coded GlobalTimeEthTSyn */ + +/** @brief Macros related to AUTOSAR TLV + */ +#define SCORE_PTP_FUP_AUTOSAR_TLV_INFO_LENGTH 10u +#define SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH 45u + +/** @brief Macros to define size of the octet for the AUTOSAR TLVs + */ +#define SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID_LENGTH 3u /* Length of ORGAID */ +#define SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE_LENGTH 3u /* Length of ORGASUBTYPE */ + +/** @brief Magic Number macro for Nanoseconds */ +#define SCORE_PTP_NANOSEC 1000000000UL + +/** @brief Max Value of Nanoseconds */ +#define SCORE_PTP_NS_UPPERLIMIT 0x3B9AC9FFU + +/** @brief Mask for extracting lower 32bits from uint64_t variable */ +#define SCORE_PTP_LOWER_DOUBLE_WORD_MASK 0x00000000FFFFFFFFUL + +/** @brief Mask for extracting upper 32bits from uint64_t variable */ +#define SCORE_PTP_HIGHER_DOUBLE_WORD_MASK 0xFFFFFFFF00000000UL + +/** @brief Mask for extracting lower 8bits from uint16_t variable */ +#define SCORE_PTP_LOWER_BYTE_MASK 0x00FFu + +/** @brief Mask for extracting upper 8bits from uint16_t variable */ +#define SCORE_PTP_HIGHER_BYTE_MASK 0xFF00u + +/** @brief Macro to for sign of Timestamp - 1 means POSITIVE timestamp */ +#define SCORE_PTP_POSITIVE 1u + +/** @brief Macro to for sign of Timestamp - 0 means NEGATIVE timestamp */ +#define SCORE_PTP_NEGATIVE 0u + +/** @brief Macro for FALSE */ +#define SCORE_PTP_FALSE 0u + +/** @brief Macro for TRUE */ +#define SCORE_PTP_TRUE (!SCORE_PTP_FALSE) + + +/** @brief FollowUp message length when message compliance is set to + * IEEE8021AS_AUTOSAR ( The packet will include AUTOSAR SubTlvs). + * Length here includes only AUTOSAR Header (excluding SubTLVs) + * The SubTLVs length have to be individually accumulated depending on + * which of them is configured. + */ +#define SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR 86u + +/** @brief FollowUp message length when message compliance is set to + * IEEE8021AS + */ +#define SCORE_PTP_FUP_PACKET_LENGTH_IEEE8021AS 76u + +/** + * @brief Length of the input stream for computing crc of userdata + * CRC shall be computed using - UserDataLength, UserByte_0, UserByte_1, UserByte_2 and DataID + */ +#define SCORE_PTP_USERDATA_CRC_STREAM_LENGTH 5u + +/** + * @brief Length of the input stream for computing crc of timeSecured subtlv + * CRC shall be computed using - CRC_Time_Flags, domainNumber/messageLength, + * sourcePortIdentity/correctionField, preciseOriginTimestamp/sequenceId and DataID + */ +#define SCORE_PTP_TIMESECURED_CRC_TIME_0_STREAM_LENGTH \ + (SCORE_PTP_FUP_MESSAGE_HEADER_DOMAIN_NUMBER_LENGTH + SCORE_PTP_FUP_MESSAGE_HEADER_PORT_IDENTITY_LENGTH + \ + SCORE_PTP_FUP_MESSAGE_PRECISE_ORIGIN_TIME_LENGTH) + +/** + * @brief Length of the input stream for computing crc of timeSecured subtlv + * CRC shall be computed using - CRC_Time_Flags, domainNumber/messageLength, + * sourcePortIdentity/correctionField, preciseOriginTimestamp/sequenceId and DataID + */ +#define SCORE_PTP_TIMESECURED_CRC_TIME_1_STREAM_LENGTH \ + (SCORE_PTP_FUP_MESSAGE_HEADER_MESSAGE_LENGTH_BYTES + SCORE_PTP_FUP_MESSAGE_HEADER_CORRECTION_FIELD_LENGTH + \ + SCORE_PTP_FUP_MESSAGE_HEADER_SEQUENCE_ID_LENGTH) + +/** + * @brief Bit Masks for CRC Time Flags + */ +#define SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK 0x01 +#define SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK 0x02 +#define SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK 0x04 +#define SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK 0x08 +#define SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK 0x10 +#define SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK 0x40 + +/** + * @brief Number of Milliseconds in one sec + */ +#define SCORE_PTP_DEFAULT_SYNC_PERIOD 1000 + +/** @brief Typedef to define sign of a number */ +typedef uint8_t Score_PtpSignType; + +/** @brief Typedef to define boolean type */ +typedef uint32_t Score_PtpBooleanType; + +/** @brief The return type for the Ptpd APIs */ +typedef enum { SCORE_PTP_E_OK = 0, SCORE_PTP_E_NOT_OK = 1 } Score_PtpReturnType; + +/** @brief Enum to identify the message type - Consumer */ +typedef enum {SCORE_PTP_INGRESS_UNDEFINED = -1, SCORE_PTP_INGRESS_T1 = 0, SCORE_PTP_INGRESS_T2 = 1 } Score_PtpIngressVltType; + +/** @brief Enum to hold the current State of the Sync-Cycle */ +typedef enum { SCORE_PTP_STATE_SYNC_RCVD = 0, SCORE_PTP_STATE_FUP_RCVD, SCORE_PTP_STATE_INVALID } Score_PtpStateType; + +/** @brief Enum to identify the message type - Provider */ +typedef enum { SCORE_PTP_EGRESS_T0 = 0, SCORE_PTP_EGRESS_T4 = 1 } Score_PtpEgressVltType; + +/** @brief Defines the type for ClockIdentity + */ +typedef char Score_PtpClockIdentityType[SCORE_PTP_CLOCK_IDENTITY_LENGTH]; + +/** @brief Defines the structure for PortIdentity + */ +typedef struct { + Score_PtpClockIdentityType clockIdentity; + uint16_t portNumber; +} Score_PtpPortIdentity; + +#pragma pack(push, 1) + +/** @brief Defines the followup header structure + */ +typedef struct { + uint8_t transportSpecific; + uint8_t messageType; + uint8_t reserved0; + uint8_t versionPtp; + uint16_t messageLength; + uint8_t domainNumber; + uint8_t reserved1; + uint8_t flagField0; + uint8_t flagField1; + uint64_t correctionField; + uint32_t reserved2; + Score_PtpPortIdentity sourcePortIdentity; + uint16_t sequenceId; + uint8_t controlField; + uint8_t logMessageInterval; +} Score_PtpFupHeaderType; + +/** @brief Defines the TimeSecured SubTLV message format + */ +typedef struct { + uint8_t subtlvType; /* Type of the Subtlv */ + uint8_t subtlvLength; /* Length of the Subtlv */ + uint8_t crcTimeFlags; /* Bitmask indicating the followup messsage fields that are used for CRC_Time_0 and CRC_Time_1 + calculation */ + uint8_t crcTime0; /* Userdata byte 0*/ + uint8_t crcTime1; /* Userdata byte 1*/ +} Score_PtpSubtlvTimeSecuredType; + +/** @brief Defines the Status (Secured and NonSecured) SubTLV message format + */ +typedef struct { + uint8_t subtlvType; /* Type of the Subtlv */ + uint8_t subtlvLength; /* Length of the Subtlv */ + uint8_t status; /* Timebase status */ + uint8_t crcStatus; /* CRC for the Status byte, not used for Status Not-Secured */ +} Score_PtpSubtlvStatusType; + +/** @brief Defines the Userdata (Secured and NonSecured) SubTLV message format + */ +typedef struct { + uint8_t subtlvType; /* Type of the Subtlv */ + uint8_t subtlvLength; /* Length of the Subtlv */ + uint8_t userdataLen; /* Length of the SubtlvType - Userdata length */ + uint8_t userdataByte0; /* Userdata byte 0*/ + uint8_t userdataByte1; /* Userdata byte 1*/ + uint8_t userdataByte2; /* Userdata byte 2*/ + uint8_t crcUserdata; /* CRC for the Userdata bytes, not used for Userdata Not-Secured*/ +} Score_PtpSubtlvUserdataType; + +/** @brief Defines the OFS data format in the Autosar SubTLV + */ +typedef struct { + /*** Todo: the exact data structure format to be decided during the feature implementation */ + uint8_t subtlvType; /* Type of the Subtlv */ + uint8_t subtlvLength; /* Length of the Subtlv */ + uint8_t OfsTimeDomain; /* Timedomain */ + uint8_t OfsTimeSec[6]; /* Seconds */ + uint8_t OfsTimeNSec[4]; /* Nanoseconds */ + uint8_t status; /* Status of the timebase */ + uint8_t userdataLen; /* Length of the SubtlvType - Userdata length */ + uint8_t userdataByte0; /* Userdata byte 0*/ + uint8_t userdataByte1; /* Userdata byte 1*/ + uint8_t userdataByte2; /* Userdata byte 2*/ + uint8_t crcUserdata; /* CRC for the Userdata bytes, not used for Userdata Not-Secured*/ +} Score_PtpSubtlvOfsType; + +/** @brief Defines structure of Autosar TLV (including SubTLV) + */ +typedef struct { + /* AUTOSAR related */ + uint16_t tlvType; /* AUTOSAR TLV type */ + uint16_t tlvLength; /* AUTOSAR TLV length */ + uint8_t orgId[SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID_LENGTH]; /* AUTOSAR Org Id*/ + uint8_t orgSubType[SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE_LENGTH]; /* AUTOSAR Org Sub Id*/ + Score_PtpSubtlvTimeSecuredType subTLVTimeSecured; + Score_PtpSubtlvStatusType subTLVStatus; + Score_PtpSubtlvUserdataType subTLVUserdata; + Score_PtpSubtlvOfsType subTLVOFS; +} Score_PtpAutosarTlvType; + +/** @brief Defines follow-up information structure as described in PTP + * protocol. It contains only the fup information for IEEE and AUTOSAR. + * The Protocol header is not included here as it is already filled by ptpd. + */ +typedef struct { + uint16_t tlvType; /* IEEE TLV type */ + uint16_t tlvLength; /* IEEE TLV type */ + uint8_t orgId[3]; /* IEEE Org Id*/ + uint8_t orgSubType[3]; /* IEEE Org Sub Id*/ + uint32_t scaledrateOffset; + uint16_t gmTimeBaseInd; + uint8_t lastGmPhaseChange[12]; + uint32_t lastGmFreqChange; + uint8_t autosarTlv[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; +} Score_PtpProtocolFupInfoType; + +#pragma pack(pop) + +/** @brief Type for expressing timestamps along with the sign */ +typedef struct { + TSync_TimeStampType ts; /** TimeStamp value */ + Score_PtpSignType sign; /** Positive (True) / negative (False) time */ +} Score_PtpSignedTimeStampType; + +/** @brief Defines various SubTlvs that are supported by AUTOSAR + */ +typedef struct { + TSync_UserDataType userdata; +} Score_PtpSubTlvType; + +/** @brief Defines the Follow_Up message fields that are required for the + * calculation of globaltime + */ +typedef struct { + Score_PtpFupHeaderType followupHeaderInfo; /* Followup Header as received in follow-up message */ + Score_PtpSignedTimeStampType preciseOriginTimestamp; /* Sync Origin timestamp as received in the follow-up message */ + Score_PtpSubTlvType subTlv; /* SubTLV information */ +} Score_PtpFupMessageType; + +/** @brief Defines the all the received information - both Sync and Fup message + * fields that are required for the calculation of globaltime + */ +typedef struct { + Score_PtpFupMessageType followUpMsg; /* Stores the follow-up message information */ + Score_PtpSignedTimeStampType correctionField; /* The received correction field converted to timestamp */ + TSync_VirtualLocalTimeType t1Vlt; /* Stores the Ingress Time Stamp of Sync message - T1 */ + TSync_VirtualLocalTimeType t2Vlt; /* Stores the Ingress Time Stamp of FUP message - T2 */ + Score_PtpStateType syncState; /* The current State of the Sync Cycle */ +} Score_PtpConsumerDataType; + +/** @brief Defines the Timestamp type compatible to PTP daemon. + */ +typedef struct { + uint32_t seconds; /* 32 bit LSB of the 48 bits Seconds part of the time */ + uint16_t secondsHi; /* 16 bit MSB of the 48 bits Seconds part of the time */ + uint32_t nanoseconds; /* nanoseconds part of the time */ +} Score_PtpTimeStampType; + +/** @brief Defines all the Provider information needed for the calculation of + * preciseOriginTime + */ +typedef struct { + Score_PtpFupMessageType followUpMsg; /* Stores the follow-up message information */ + TSync_VirtualLocalTimeType t0Vlt; /* Stores the Egress Virtual Local Time of Sync message - T0 */ + TSync_VirtualLocalTimeType t4Vlt; /* Stores the Egress Virtual Local Time when Sync message was on the Bus - T4 */ + TSync_TimeStampType t0GlobalTime; /* Stores the Egress Global Time of Sync message - T0 */ + volatile Score_PtpBooleanType immediateTimeTransmission; /* Indicates if ImmediateTimeTransmission is needed. TRUE = Transmit immediately, + FALSE = No immediate transmission requested */ +} Score_PtpProviderDataType; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SCORE_PTP_TYPES_H */ diff --git a/src/ptpd/src/score/src/score_ptp_arith.c b/src/ptpd/src/score/src/score_ptp_arith.c new file mode 100644 index 0000000..33ba785 --- /dev/null +++ b/src/ptpd/src/score/src/score_ptp_arith.c @@ -0,0 +1,267 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "../inc/score_ptp_arith.h" + +#include +#include + +/** @brief Algorithm to add two timestamps. + * @details + * This function enables the user to add two timestamps. The timestamps + * have sign as well. Hence as per the sign, the addition/subtraction shall be + * performed. + * @param[in] timeStampIn1 First Timestamp operand. + * @param[in] timeStampIn2 Second Timestamp operand. + * @param[out] timeStampResultOut The result is of the operation is + * updated here. + * @return None. + */ +void Score_PtpCalculateTimeSum(const Score_PtpSignedTimeStampType *timeStampIn1, + const Score_PtpSignedTimeStampType *timeStampIn2, + Score_PtpSignedTimeStampType *timeStampResultOut) { + /* local variable declaration */ + uint64_t timeStamp1; + uint64_t timeStamp2; + uint64_t timeStampSum; + + /* Convert the TimeStamps(seconds part) to uint64_t */ + timeStamp1 = + (uint64_t)((((uint64_t)(timeStampIn1->ts.secondsHi)) << 32u) | (timeStampIn1->ts.seconds)); + timeStamp2 = + (uint64_t)((((uint64_t)(timeStampIn2->ts.secondsHi)) << 32u) | (timeStampIn2->ts.seconds)); + timeStampSum = 0u; + + /* Check signs of both Timestamps are same */ + if (timeStampIn1->sign == timeStampIn2->sign) { + /* Add nanoseconds part */ + timeStampResultOut->ts.nanoseconds = timeStampIn1->ts.nanoseconds + timeStampIn2->ts.nanoseconds; + + /* Add seconds part now*/ + timeStampSum = timeStamp1 + timeStamp2; + + /* If nanoseconds is greater than 999999999(0x3B9AC9FFU), max + nanoseconds are reached. Time to increment the seconds count by 1 */ + if (SCORE_PTP_NS_UPPERLIMIT < timeStampResultOut->ts.nanoseconds) { + timeStampSum += 1u; + timeStampResultOut->ts.nanoseconds -= SCORE_PTP_NANOSEC; + } + /* Take over the sign from either of the TimeStamps - as both times are + same */ + timeStampResultOut->sign = timeStampIn1->sign; + } + /* Since the signs are different, subtraction to be carried out */ + else { + /* The seconds part of both the Timestamps are same */ + if (timeStamp1 == timeStamp2) { + timeStampSum = 0u; + + /* Nanoseconds of first TimeStamp is greater than the seconds */ + if (timeStampIn1->ts.nanoseconds >= timeStampIn2->ts.nanoseconds) { + timeStampResultOut->ts.nanoseconds = timeStampIn1->ts.nanoseconds - timeStampIn2->ts.nanoseconds; + timeStampResultOut->sign = timeStampIn1->sign; + } else { + timeStampResultOut->ts.nanoseconds = timeStampIn2->ts.nanoseconds - timeStampIn1->ts.nanoseconds; + timeStampResultOut->sign = timeStampIn2->sign; + } + } + /* The seconds part of first TimeStamp is greater than second */ + else if (timeStamp1 > timeStamp2) { + /* Nanoseconds of first TimeStamp is greater than the seconds */ + if (timeStampIn1->ts.nanoseconds >= timeStampIn2->ts.nanoseconds) { + timeStampResultOut->ts.nanoseconds = timeStampIn1->ts.nanoseconds - timeStampIn2->ts.nanoseconds; + timeStampSum = timeStamp1 - timeStamp2; + timeStampResultOut->sign = timeStampIn1->sign; + } else { + timeStampResultOut->ts.nanoseconds = + (timeStampIn1->ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn2->ts.nanoseconds; + timeStampSum = (timeStamp1 - 1u) - timeStamp2; + timeStampResultOut->sign = timeStampIn1->sign; + } + } else { + if (timeStampIn1->ts.nanoseconds > timeStampIn2->ts.nanoseconds) { + timeStampResultOut->ts.nanoseconds = + (timeStampIn2->ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn1->ts.nanoseconds; + timeStampSum = (timeStamp2 - 1u) - timeStamp1; + timeStampResultOut->sign = timeStampIn2->sign; + } else { + timeStampResultOut->ts.nanoseconds = timeStampIn2->ts.nanoseconds - timeStampIn1->ts.nanoseconds; + timeStampSum = timeStamp2 - timeStamp1; + timeStampResultOut->sign = timeStampIn2->sign; + } + } + } + /* Updating seconds and secondsHi of timeStampResultOut from local variable */ + timeStampResultOut->ts.secondsHi = + (uint16_t)(((timeStampSum) & (SCORE_PTP_HIGHER_DOUBLE_WORD_MASK)) >> 32u); + timeStampResultOut->ts.seconds = (uint32_t)((timeStampSum) & (SCORE_PTP_LOWER_DOUBLE_WORD_MASK)); +} /* End of function Score_PtpCalculateTimeSum */ + +/** @brief Algorithm to subtract two timestamps. + * @details + * This function enables the user to subtract two timestamps. + * @param[in] timeStampIn1 First Timestamp operand. + * @param[in] timeStampIn2 Second Timestamp operand. + * @param[out] timeStampResultOut The result is of the operation is + * updated here. + * @return None. + */ +void Score_PtpCalculateTimeDiff(const Score_PtpSignedTimeStampType *timeStampIn1, + const Score_PtpSignedTimeStampType *timeStampIn2, + Score_PtpSignedTimeStampType *timeStampResultOut) { + /* local variable declaration */ + uint64_t timeStamp1; + uint64_t timeStamp2; + uint64_t timeStampDiff; + + /* Convert the TimeStamps(seconds part) to uint64_t */ + timeStamp1 = (uint64_t)((((uint64_t)(timeStampIn1->ts.secondsHi)) << 32) | (timeStampIn1->ts.seconds)); + timeStamp2 = (uint64_t)((((uint64_t)(timeStampIn2->ts.secondsHi)) << 32) | (timeStampIn2->ts.seconds)); + + timeStampDiff = 0u; + + /* The seconds part are equal */ + if (timeStamp1 == timeStamp2) { + timeStampDiff = 0u; + + /* First operand is greater, hence carry out the subtraction and take + over the POSITIVE sign */ + if (timeStampIn1->ts.nanoseconds >= timeStampIn2->ts.nanoseconds) { + timeStampResultOut->ts.nanoseconds = timeStampIn1->ts.nanoseconds - timeStampIn2->ts.nanoseconds; + timeStampResultOut->sign = SCORE_PTP_POSITIVE; + } + /* Since the second operand is greater, take over the Negative sign */ + else { + timeStampResultOut->ts.nanoseconds = timeStampIn2->ts.nanoseconds - timeStampIn1->ts.nanoseconds; + timeStampResultOut->sign = SCORE_PTP_NEGATIVE; + } + } + /* First operand is greater */ + else if (timeStamp1 > timeStamp2) { + /* The sign in both the cases below would be POSITIVE as the first + operand is greater */ + /* When nanoseconds of first operand is greater than second, simple + subtraction can be carried */ + if (timeStampIn1->ts.nanoseconds >= timeStampIn2->ts.nanoseconds) { + timeStampResultOut->ts.nanoseconds = timeStampIn1->ts.nanoseconds - timeStampIn2->ts.nanoseconds; + timeStampDiff = timeStamp1 - timeStamp2; + timeStampResultOut->sign = SCORE_PTP_POSITIVE; + } + /* When nanoseconds of first operand is smaller than second(however the + seconds part of the first operand is greater ), a borrow operation is + necessary */ + else { + timeStampResultOut->ts.nanoseconds = + (timeStampIn1->ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn2->ts.nanoseconds; + timeStampDiff = (timeStamp1 - 1u) - timeStamp2; + timeStampResultOut->sign = SCORE_PTP_POSITIVE; + } + } + /* Second operand is greater */ + else { + /* The sign in both the cases below would be NEGATIVE as the second + operand is greater than first */ + if (timeStampIn1->ts.nanoseconds > timeStampIn2->ts.nanoseconds) { + timeStampResultOut->ts.nanoseconds = + (timeStampIn2->ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn1->ts.nanoseconds; + timeStampDiff = (timeStamp2 - 1u) - timeStamp1; + timeStampResultOut->sign = SCORE_PTP_NEGATIVE; + } else { + timeStampResultOut->ts.nanoseconds = timeStampIn2->ts.nanoseconds - timeStampIn1->ts.nanoseconds; + timeStampDiff = timeStamp2 - timeStamp1; + timeStampResultOut->sign = SCORE_PTP_NEGATIVE; + } + } + /* Update seconds and secondsHi of timeStampResultOut from local variable */ + timeStampResultOut->ts.secondsHi = + (uint16_t)(((timeStampDiff) & (SCORE_PTP_HIGHER_DOUBLE_WORD_MASK)) >> 32u); + timeStampResultOut->ts.seconds = (uint32_t)((timeStampDiff) & (SCORE_PTP_LOWER_DOUBLE_WORD_MASK)); + +} /* End of function Score_PtpCalculateTimeDiff */ + +/** @brief Algorithm to convert uint64_t(nanoseconds) value to TimeStamp stamp. + * @details + * This function enables the user convert uint64_t value into TimeStamp type. + * It also initializes the sign of the TimeStamp generated as POSITIVE. + * @param[in] timeValue Time value in 64 bits format. + * @param[in] factor Number of bits to shift. + * @param[out] timeStampResultOut The result is of the operation is updated + * here. + * @return None. + */ +void Score_PtpConvertToTimeStamp(uint64_t timeValue, uint8_t factor, Score_PtpSignedTimeStampType *timeStampResultOut) { + /* Local variable declaration */ + uint64_t timeValueTemp; + + /* Convert from Bitfield to Time */ + timeValueTemp = timeValue >> factor; + + /* Obtain nanoseconds part of time */ + timeStampResultOut->ts.nanoseconds = (uint32_t)(timeValueTemp % SCORE_PTP_NANOSEC); + + /* Obtain Seconds part of time */ + timeValueTemp = timeValueTemp / SCORE_PTP_NANOSEC; + + /* Initialising seconds and secondsHi of Time */ + timeStampResultOut->ts.secondsHi = + (uint16_t)(((timeValueTemp) & (SCORE_PTP_HIGHER_DOUBLE_WORD_MASK)) >> 32u); + timeStampResultOut->ts.seconds = (uint32_t)((timeValueTemp) & (SCORE_PTP_LOWER_DOUBLE_WORD_MASK)); + + /* Initializes the sign */ + timeStampResultOut->sign = SCORE_PTP_POSITIVE; + +} /* End of function Score_PtpConvertToTimeStamp */ + +/** @brief Algorithm to interpolate the global time based on VirtualLocalTimes. + * @details + * This function interpolates the global time based on the passed + * VirtualLocalTime. The computation is as follows : + * new_global_time = + * global_time_passed + (new_virtual_local_time - old_virtual_local_time) + * @param[in] vltCurrentIn The new Virtual Local time. + * @param[in] vltPreviousIn The previous/old Virtual Local Time. + * @param[in] globalTimeStampPreviousIn The previous/old global time. + * @param[out] resultCurrentTimeStampOut Pointer to newly computed global + * time. + * @return None. + */ +void Score_PtpInterpolateTime(const TSync_VirtualLocalTimeType *vltCurrentIn, + const TSync_VirtualLocalTimeType *vltPreviousIn, + const Score_PtpSignedTimeStampType *globalTimeStampPreviousIn, + Score_PtpSignedTimeStampType *resultCurrentTimeStampOut) { + Score_PtpSignedTimeStampType vltCurrentTimeStamp; + Score_PtpSignedTimeStampType vltPreviousTimeStamp; + Score_PtpSignedTimeStampType vltDiff; + Score_PtpSignedTimeStampType globalTimeResult; + uint64_t vltCurrentTimeValue, vltPreviousTimeValue = 0u; + + memset((void *)(&vltCurrentTimeStamp.ts), 0u, sizeof(TSync_TimeStampType)); + vltCurrentTimeStamp.sign = SCORE_PTP_POSITIVE; + + memset((void *)(&vltPreviousTimeStamp.ts), 0u, sizeof(TSync_TimeStampType)); + vltPreviousTimeStamp.sign = SCORE_PTP_POSITIVE; + + memset((void *)(&vltDiff.ts), 0u, sizeof(TSync_TimeStampType)); + vltDiff.sign = SCORE_PTP_POSITIVE; + + memset((void *)(&globalTimeResult.ts), 0u, sizeof(TSync_TimeStampType)); + globalTimeResult.sign = SCORE_PTP_POSITIVE; + + vltCurrentTimeValue = ((uint64_t)vltCurrentIn->nanosecondsHi << 32) | vltCurrentIn->nanosecondsLo; + vltPreviousTimeValue = ((uint64_t)vltPreviousIn->nanosecondsHi << 32) | vltPreviousIn->nanosecondsLo; + + /* Convert and store the current time */ + Score_PtpConvertToTimeStamp(vltCurrentTimeValue, 0u, &vltCurrentTimeStamp); + + /* Convert and store the current time */ + Score_PtpConvertToTimeStamp(vltPreviousTimeValue, 0u, &vltPreviousTimeStamp); + + Score_PtpCalculateTimeDiff(&vltCurrentTimeStamp, &vltPreviousTimeStamp, &vltDiff); + + Score_PtpCalculateTimeSum(globalTimeStampPreviousIn, &vltDiff, &globalTimeResult); + + resultCurrentTimeStampOut->ts.secondsHi = globalTimeResult.ts.secondsHi; + resultCurrentTimeStampOut->ts.seconds = globalTimeResult.ts.seconds; + resultCurrentTimeStampOut->ts.nanoseconds = globalTimeResult.ts.nanoseconds; +} diff --git a/src/ptpd/src/score/src/score_ptp_common.c b/src/ptpd/src/score/src/score_ptp_common.c new file mode 100644 index 0000000..8bf00ae --- /dev/null +++ b/src/ptpd/src/score/src/score_ptp_common.c @@ -0,0 +1,285 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "inc/score_ptp_common.h" + +#include +#include +#include + +#include "../../dep/constants_dep.h" +#include "../../ptp_datatypes.h" + +/** @brief Type for storing the configuration information and information that + * is derived from the configuration. Configuration info mainly comes from the + * command line. + */ +Score_PtpConfigType ptpConfig; + +/** @brief Defines the all the received information - both Sync and Fup message + * fields that are required for the calculation of globaltime + */ +extern Score_PtpConsumerDataType consumerData; + +/** @brief Consolidates the all the provider timing information to compute + * PreciseOriginTime + */ +extern Score_PtpProviderDataType providerData; + +/** @brief Updates the global variable for CRC time flags. + * @details + * This function updates the Bit masks in global variable according to + * configured time flags. + * @param[in] None + * @return None. + */ +static void Score_PtpUpdateCrcTimeFlags(void) { + if (ptpConfig.timebaseConfig.crcFlags.message_length) + { + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK); + } + if (ptpConfig.timebaseConfig.crcFlags.domain_number) + { + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK); + } + if (ptpConfig.timebaseConfig.crcFlags.correction_field) + { + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK); + } + if (ptpConfig.timebaseConfig.crcFlags.source_port_identity) + { + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK); + } + if (ptpConfig.timebaseConfig.crcFlags.sequence_id) + { + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK); + } + if (ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp) + { + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK); + } +} + +/** @brief Init function to update configuration information. + * @details + * This function stores the configuration information into a global structure. + * The information is received from the command line options. + * @param[in] pdelay Static Propagation delay. + * @param[in] domainNumber Time Base Identifier. + * @param[in] isProvider If the ptpd instance is Provider. + * @return Score_PtpReturnType SCORE_PTP_E_OK for successful initialization + * SCORE_PTP_E_NOT_OK otherwise. + */ +Score_PtpReturnType Score_PtpInitialize(double pdelay, uint16_t domainNumber, Score_PtpBooleanType isProvider) { + Score_PtpReturnType returnValue = SCORE_PTP_E_NOT_OK; + + /* For the received TimeBase, the handle must be derived from the + TSync-Lib. This handle will be used for making tsync calls related to + the above timebaseId */ + if (E_OK == TSync_Open()) { + ptpConfig.timebaseHandle = TSync_OpenTimebase((TSync_SynchronizedTimeBaseType)(domainNumber)); + + if (NULL != ptpConfig.timebaseHandle) { + /* Update the timebase id */ + ptpConfig.timebaseId = domainNumber; + + if (isProvider) { + if (E_OK != TSync_RegisterTransmitGlobalTimeCallback(ptpConfig.timebaseHandle, + Score_PtpTransmitGlobalTimeCallback)) { + return returnValue; + } + } + + } else { + ptpConfig.timebaseHandle = NULL; + fprintf(stderr, + "[ERROR] aptpd2: Timebase handle is NULL. Basic " + "initialization failed.\n"); + return returnValue; + } + } else { + ptpConfig.timebaseHandle = NULL; + fprintf(stderr, "[ERROR] aptpd2: TSync_Open failed. Basic initialization failed.\n"); + return returnValue; + } + + if (SCORE_PTP_E_OK == Score_PtpConfigure(isProvider)) { + /* Update pdelay - Convert the received pdelay into TimeStamp type */ + ptpConfig.pdelay.ts.secondsHi = 0u; + ptpConfig.pdelay.ts.seconds = trunc(pdelay); + ptpConfig.pdelay.ts.nanoseconds = (pdelay - (ptpConfig.pdelay.ts.seconds + 0.0)) * 1E9; + + /* Updating the default sign as POSITIVE */ + ptpConfig.pdelay.sign = SCORE_PTP_POSITIVE; + + /* Update the default Sync-State to INVALID */ + consumerData.syncState = SCORE_PTP_STATE_INVALID; + + returnValue = SCORE_PTP_E_OK; + +#ifdef PTPD_SCORE_DEBUG + printf("\n[DEBUG] aptpd2: The config values are : "); + printf( + "\n[DEBUG] aptpd2: Pdelay : %d secondsHi, %d seconds and %d " + "nanoseconds", + ptpConfig.pdelay.ts.secondsHi, ptpConfig.pdelay.ts.seconds, ptpConfig.pdelay.ts.nanoseconds); + + printf("\n[DEBUG] aptpd2: DomainNumber : %d", ptpConfig.timebaseId); + printf("\n[DEBUG] aptpd2: Crc support(For Provider) : %d", ptpConfig.timebaseConfig.crcSupport); + printf("\n[DEBUG] aptpd2: Crc Validation(For Consumer) : %d", ptpConfig.timebaseConfig.crcValidation); + printf("\n[DEBUG] aptpd2: messageCompliance : %d", ptpConfig.timebaseConfig.messageCompliance); + printf("\n[DEBUG] aptpd2: followUpTimeSubTLV : %d", + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV); + printf("\n[DEBUG] aptpd2: followUpStatusSubTLV : %d", + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV); + printf("\n[DEBUG] aptpd2: followUpUserDataSubTLV : %d", + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV); + printf("\n[DEBUG] aptpd2: correction_field : %d", + ptpConfig.timebaseConfig.crcFlags.correction_field); + printf("\n[DEBUG] aptpd2: domain_number : %d", + ptpConfig.timebaseConfig.crcFlags.domain_number); + printf("\n[DEBUG] aptpd2: message_length : %d", + ptpConfig.timebaseConfig.crcFlags.message_length); + printf("\n[DEBUG] aptpd2: precise_origin_timestamp : %d", + ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp); + printf("\n[DEBUG] aptpd2: sequence_id : %d", + ptpConfig.timebaseConfig.crcFlags.sequence_id); + printf("\n[DEBUG] aptpd2: source_port_identity : %d", + ptpConfig.timebaseConfig.crcFlags.source_port_identity); + printf("\n[DEBUG] aptpd2: syncPeriodMs : %d", + ptpConfig.timebaseConfig.syncPeriodMs); +#endif + } + + return returnValue; +} + +/** @brief Deinit function to perform graceful shutdown. + * @details + * This functions performs all the activities associated with graceful shutdown + * - Like close the connection with TSync, set the tsync handle to NULL. + * @param None. + * @return None. + */ +void Score_PtpDeinitialize(void) { + /* Close the TimeBase */ + TSync_CloseTimebase(ptpConfig.timebaseHandle); + + /* Set the timebase handle to NULL */ + ptpConfig.timebaseHandle = NULL; + + /* Update the Sync-State to Invalid */ + consumerData.syncState = SCORE_PTP_STATE_INVALID; + + /* Close the connection to TSync */ + TSync_Close(); + +} + +/** @brief Configure function to read configuration from tsync-ptp-lib. + * @details + * This function retrieves the configuration data from tsync-ptp-lib during + * the startup. The configuation is stored into the global configuration + * structure. + * @return Score_PtpReturnType SCORE_PTP_E_OK for successful config update + * SCORE_PTP_E_NOT_OK otherwise. + */ +Score_PtpReturnType Score_PtpConfigure(Score_PtpBooleanType isProvider) { + TSync_Role roleRequested = isProvider ? TSYNC_ROLE_MASTER : TSYNC_ROLE_SLAVE; + + if (E_OK == TSync_GetTimebaseConfiguration(ptpConfig.timebaseHandle, roleRequested, &ptpConfig.timebaseConfig)) { + /* If AUTOSAR packet is chosen write/read SubTLVs too to/from ptpd buffer */ + if (TSYNC_MC_IEEE8021AS_AUTOSAR == ptpConfig.timebaseConfig.messageCompliance) { + ptpConfig.configGlobals.followupLength = SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR; + + /** Check all the SubTLVs configured and accordingly update the + * follow-up length */ + /* Do not consider TimeSecured SubTLV if overall crcValidation is disabled */ + if(isProvider) { + /* For the Provider, TimeSecured SubTLV is enabled if crcSupport is enabled */ + if((TSYNC_CRC_SUPPORTED == ptpConfig.timebaseConfig.crcSupport) && + (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV)) { + ptpConfig.configGlobals.followupLength += sizeof(Score_PtpSubtlvTimeSecuredType); + ptpConfig.configGlobals.configSubTlvLength += sizeof(Score_PtpSubtlvTimeSecuredType); + Score_PtpUpdateCrcTimeFlags(); + } + } + else { + /* For the Consumer, TimeSecured SubTLV is enabled if crcValidation is other than TSYNC_CRC_NOT_VALIDATED */ + if((TSYNC_CRC_NOT_VALIDATED != ptpConfig.timebaseConfig.crcValidation) && + (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV)) { + ptpConfig.configGlobals.followupLength += sizeof(Score_PtpSubtlvTimeSecuredType); + ptpConfig.configGlobals.configSubTlvLength += sizeof(Score_PtpSubtlvTimeSecuredType); + Score_PtpUpdateCrcTimeFlags(); + } + } + if (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV) { + ptpConfig.configGlobals.followupLength += sizeof(Score_PtpSubtlvStatusType); + ptpConfig.configGlobals.configSubTlvLength += sizeof(Score_PtpSubtlvStatusType); + } + if (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV) { + ptpConfig.configGlobals.followupLength += sizeof(Score_PtpSubtlvUserdataType); + ptpConfig.configGlobals.configSubTlvLength += sizeof(Score_PtpSubtlvUserdataType); + } + } + /* Use default follow-up length as configured as per IEEE8021AS */ + else { + ptpConfig.configGlobals.followupLength = SCORE_PTP_FUP_PACKET_LENGTH_IEEE8021AS; + } + + /* Check if the syncPeriodMs is configured or not */ + if(0 == ptpConfig.timebaseConfig.syncPeriodMs) { + /* Set the default value to 1 second */ + ptpConfig.timebaseConfig.syncPeriodMs = SCORE_PTP_DEFAULT_SYNC_PERIOD; + } + return SCORE_PTP_E_OK; + } + fprintf(stderr, "[ERROR] aptpd2: Could not fetch configuration via TSync_GetTimebaseConfiguration\n"); + return SCORE_PTP_E_NOT_OK; +} + +/** @brief Stores the followup header information. + * @details + * This function reads the followup header information from the ptpd data structures(open source data structure) + * into the global structure used by SCORE extensions.The values in this header files are as received by the protocol + * expect that they are converted from big endian to host format. + * @return None. + */ +void Score_PtpExtractFupHeaderInfo(const struct MsgHeaderTag* followupHeaderInfoIn, Score_PtpBooleanType isProvider) { + if (NULL != followupHeaderInfoIn) { + if (isProvider) { + providerData.followUpMsg.followupHeaderInfo.transportSpecific = followupHeaderInfoIn->transportSpecific; + providerData.followUpMsg.followupHeaderInfo.messageType = followupHeaderInfoIn->messageType; + providerData.followUpMsg.followupHeaderInfo.reserved0 = followupHeaderInfoIn->reserved0; + providerData.followUpMsg.followupHeaderInfo.versionPtp = followupHeaderInfoIn->versionPTP; + providerData.followUpMsg.followupHeaderInfo.messageLength = followupHeaderInfoIn->messageLength; + providerData.followUpMsg.followupHeaderInfo.domainNumber = followupHeaderInfoIn->domainNumber; + providerData.followUpMsg.followupHeaderInfo.flagField0 = followupHeaderInfoIn->flagField0; + providerData.followUpMsg.followupHeaderInfo.flagField1 = followupHeaderInfoIn->flagField1; + providerData.followUpMsg.followupHeaderInfo.correctionField = + ((followupHeaderInfoIn->correctionField.msb << 32) | (followupHeaderInfoIn->correctionField.lsb)); + memcpy((&providerData.followUpMsg.followupHeaderInfo.sourcePortIdentity), + (&followupHeaderInfoIn->sourcePortIdentity), sizeof(PortIdentity)); + providerData.followUpMsg.followupHeaderInfo.sequenceId = followupHeaderInfoIn->sequenceId; + providerData.followUpMsg.followupHeaderInfo.controlField = followupHeaderInfoIn->controlField; + providerData.followUpMsg.followupHeaderInfo.logMessageInterval = followupHeaderInfoIn->logMessageInterval; + } else { + consumerData.followUpMsg.followupHeaderInfo.transportSpecific = followupHeaderInfoIn->transportSpecific; + consumerData.followUpMsg.followupHeaderInfo.messageType = followupHeaderInfoIn->messageType; + consumerData.followUpMsg.followupHeaderInfo.reserved0 = followupHeaderInfoIn->reserved0; + consumerData.followUpMsg.followupHeaderInfo.versionPtp = followupHeaderInfoIn->versionPTP; + consumerData.followUpMsg.followupHeaderInfo.messageLength = followupHeaderInfoIn->messageLength; + consumerData.followUpMsg.followupHeaderInfo.domainNumber = followupHeaderInfoIn->domainNumber; + consumerData.followUpMsg.followupHeaderInfo.flagField0 = followupHeaderInfoIn->flagField0; + consumerData.followUpMsg.followupHeaderInfo.flagField1 = followupHeaderInfoIn->flagField1; + consumerData.followUpMsg.followupHeaderInfo.correctionField = + ((followupHeaderInfoIn->correctionField.msb << 32) | (followupHeaderInfoIn->correctionField.lsb)); + memcpy((void*)(&consumerData.followUpMsg.followupHeaderInfo.sourcePortIdentity), + (void*)(&followupHeaderInfoIn->sourcePortIdentity), sizeof(PortIdentity)); + consumerData.followUpMsg.followupHeaderInfo.sequenceId = followupHeaderInfoIn->sequenceId; + consumerData.followUpMsg.followupHeaderInfo.controlField = followupHeaderInfoIn->controlField; + consumerData.followUpMsg.followupHeaderInfo.logMessageInterval = followupHeaderInfoIn->logMessageInterval; + } + } +} diff --git a/src/ptpd/src/score/src/score_ptp_consumer.c b/src/ptpd/src/score/src/score_ptp_consumer.c new file mode 100644 index 0000000..3c222ac --- /dev/null +++ b/src/ptpd/src/score/src/score_ptp_consumer.c @@ -0,0 +1,233 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "inc/score_ptp_consumer.h" + +#include +#include + +#include "inc/score_ptp_types.h" +#include "inc/score_ptp_arith.h" +#include "inc/score_ptp_common.h" +#include "inc/score_ptp_subtlv.h" +#include "inc/score_ptp_config.h" + +/** @brief The structure that is used to store the information for computation + * of globalTime */ +Score_PtpConsumerDataType consumerData; + +/** @brief Type for storing the configuration information and information that + * is derived from the configuration. Configuration info mainly comes from the + * command line. + */ +extern Score_PtpConfigType ptpConfig; + +/** @brief Function to update the follow-up information received . + * @details + * This function stores the follow-up information into a global data structure. + * The information is originally received from the follow-up message. + * @param[in] originTimeIn Precise Origin Time. + * @param[in] corrTimeIn Correction field. + * @param[in] fupInfoIn Pointer to buffer containing fup info. + * @return None. + */ +void Score_PtpReadFupInfoFromBuffer(const uint8_t *originTimeIn, const uint8_t *corrTimeIn, const uint8_t *fupInfoIn) { + if ((NULL != fupInfoIn) && (NULL != originTimeIn)) { + /* Type cast the information received to TimeStamp type */ + TSync_TimeStampType *correctionField = (TSync_TimeStampType *)corrTimeIn; + + Score_PtpTimeStampType *originTime = (Score_PtpTimeStampType *)originTimeIn; + + /* Proceed only if a valid Sync message VLT was updated earlier */ + if (SCORE_PTP_STATE_SYNC_RCVD == consumerData.syncState) { + /* Update the Correction Field Value in Consumer structure */ + consumerData.correctionField.ts.secondsHi = correctionField->secondsHi; + consumerData.correctionField.ts.seconds = correctionField->seconds; + consumerData.correctionField.ts.nanoseconds = correctionField->nanoseconds; + + /* Set default sign of the time to POSITIVE */ + consumerData.correctionField.sign = SCORE_PTP_POSITIVE; + + /* Update the Origin Time in Consumer structure */ + consumerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi = originTime->secondsHi; + consumerData.followUpMsg.preciseOriginTimestamp.ts.seconds = originTime->seconds; + consumerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds = originTime->nanoseconds; + + /* Set default sign of the time to POSITIVE */ + consumerData.followUpMsg.preciseOriginTimestamp.sign = SCORE_PTP_POSITIVE; + + /* Forward the time values to timesync only if SubTLVs were read successfully */ + if (Score_PtpReadSubTlvFromBuffer(fupInfoIn)) { + Score_PtpBusSetGlobalTime(); + } else { + fprintf( + stderr, + "[ERROR] aptpd2: Autosar SubTLV Validation failure. Follow-Up message will be dropped. \n"); + } + } else { + fprintf(stderr, + "[ERROR] aptpd2: The Sync-State is not in SYNC-RECEIVED. FUP message will not be processed \n"); + } + } +} /* End of function Score_PtpReadFupInfoFromBuffer */ + +/** @brief Update the VirtualLocalTime for given message type. + * @details + * This functions gets the Virtual local Time from the TSync and updates it in + * the consumer data structure. Dependending on the argument - Sync or FUP + * the VLT will be updated in the respective variable. These values are T1 and + * T2 for the globalTime computation. + * @param vltIngress SCORE_PTP_INGRESS_T1 for updating T1 + * SCORE_PTP_INGRESS_T2 for updating T2. + * @return None. + */ +void Score_PtpReadIngressVlt(Score_PtpIngressVltType vltIngress) { + TSync_ReturnType returnValue = E_NOT_OK; + TSync_VirtualLocalTimeType virtualLocalTime = {0u, 0u}; + + /* Get the Current Virtual Local Time */ + returnValue = TSync_GetCurrentVirtualLocalTime(ptpConfig.timebaseHandle, &virtualLocalTime); + + if (E_NOT_OK == returnValue) { + fprintf( + stderr, + "[ERROR] aptpd2: In function Score_PtpReadIngressVlt, failed to get Virtual Local Time from tsync-ptp-lib\n"); + + /* Update the Sync-State to Invalid */ + consumerData.syncState = SCORE_PTP_STATE_INVALID; + } else { + /* Get TSync Time Stamp and update in the appropriate field */ + switch (vltIngress) { + case SCORE_PTP_INGRESS_T1: + consumerData.t1Vlt.nanosecondsHi = virtualLocalTime.nanosecondsHi; + + consumerData.t1Vlt.nanosecondsLo = virtualLocalTime.nanosecondsLo; + + /* Update the Sync-State to Sync-Received */ + consumerData.syncState = SCORE_PTP_STATE_SYNC_RCVD; + break; + + case SCORE_PTP_INGRESS_T2: + consumerData.t2Vlt.nanosecondsHi = virtualLocalTime.nanosecondsHi; + + consumerData.t2Vlt.nanosecondsLo = virtualLocalTime.nanosecondsLo; + + /* Update the Sync-State to Fup received */ + consumerData.syncState = SCORE_PTP_STATE_FUP_RCVD; + break; + default: + break; + } + } +} /* End of function Score_PtpReadIngressVlt */ + +/** @brief Computes the globalTime and calls BusSetGlobalTime. + * @details + * This functions performs the globalTime computation and makes call to + * BusSetGlobalTime. The fomula for globalTime computation is: + * globalTime = PreciseOriginTime + correctionfield + (T2vlt-T1vlt) + Pdelay; + * localTime = T2vlt, + * measureData = Pdelay + * @param None + * @return None. + */ +void Score_PtpBusSetGlobalTime(void) { + TSync_ReturnType returnValue = E_NOT_OK; + + Score_PtpSignedTimeStampType timeStampDiff = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType offset = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType syncTime = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType addedTime = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType finalGlobalTime = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType syncTimeStamp = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType fupTimeStamp = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + uint64_t syncRxTimeStamp = 0u; + uint64_t fupRxTimeStamp = 0u; + + /* Variables that are used to send time to TSync Lib */ + TSync_TimeStampType globalTime = {0u, 0u, 0u, 0u}; + TSync_UserDataType userdata = {0u, 0u, 0u, 0u}; + TSync_MeasurementType measureData = {0u}; + TSync_VirtualLocalTimeType localTime = {0u, 0u}; + + /* Update the T2 Time - Virtual Local Time Type */ + Score_PtpReadIngressVlt(SCORE_PTP_INGRESS_T2); + + /* Proceed only if a valid FUP message VLT was successful */ + /* Protection to variable consumerData is not needed as the + control-flow is synchronous */ + if (SCORE_PTP_STATE_FUP_RCVD == consumerData.syncState) { + /* Update the pdelay */ + measureData.pathDelay = ptpConfig.pdelay.ts.nanoseconds; + + /* Update the virtual local time to send to TSync-Lib */ + localTime.nanosecondsLo = consumerData.t2Vlt.nanosecondsLo; + localTime.nanosecondsHi = consumerData.t2Vlt.nanosecondsHi; + + /* Left shift nanoseconds high position by 32 times and perform OR operation with nanoseconds low value -- for + * T1 */ + syncRxTimeStamp = (uint64_t)((((uint64_t)consumerData.t1Vlt.nanosecondsHi) << 32u) | + consumerData.t1Vlt.nanosecondsLo); + + /* Left shift nanoseconds high position by 32 times and perform OR operation with nanoseconds low value -- for + * T2 */ + fupRxTimeStamp = (uint64_t)((((uint64_t)consumerData.t2Vlt.nanosecondsHi) << 32u) | + consumerData.t2Vlt.nanosecondsLo); + + /* Convert and store the current time in Eth_TimeIntDiffType structure */ + Score_PtpConvertToTimeStamp(syncRxTimeStamp, 0u, &timeStampDiff); + + syncTimeStamp.ts = timeStampDiff.ts; + + /* Convert and store the current time in Eth_TimeIntDiffType structure */ + Score_PtpConvertToTimeStamp(fupRxTimeStamp, 0u, &timeStampDiff); + + fupTimeStamp.ts = timeStampDiff.ts; + + /* Obtain time difference between Provider and Consumer by subtracting Ingress Time from PreciseOriginTimeStamp + * obtained in Followup frame */ + Score_PtpCalculateTimeDiff(&fupTimeStamp, &syncTimeStamp, &offset); + + /* Add the offset time to Consumer to sync time with Providers's */ + Score_PtpCalculateTimeSum(&consumerData.followUpMsg.preciseOriginTimestamp, &offset, &syncTime); + + /* Add Path delay to the time obtained*/ + Score_PtpCalculateTimeSum(&syncTime, &ptpConfig.pdelay, &addedTime); + + /* Add CorrectionField to the time difference */ + Score_PtpCalculateTimeSum(&addedTime, &consumerData.correctionField, &finalGlobalTime); + + /* Assign the obtained time to a variable of type TSync_TimeStampType */ + globalTime.nanoseconds = finalGlobalTime.ts.nanoseconds; + globalTime.seconds = finalGlobalTime.ts.seconds; + globalTime.secondsHi = finalGlobalTime.ts.secondsHi; + + userdata.userDataLength = consumerData.followUpMsg.subTlv.userdata.userDataLength; + userdata.userByte0 = consumerData.followUpMsg.subTlv.userdata.userByte0; + userdata.userByte1 = consumerData.followUpMsg.subTlv.userdata.userByte1; + userdata.userByte2 = consumerData.followUpMsg.subTlv.userdata.userByte2; + +#if PTPD_SCORE_DEBUG + printf("\n[DEBUG] aptpd2: Sending the Global Time to TSync %u secondsHI, %u seconds and %u nanoseconds", + globalTime.secondsHi, globalTime.seconds, globalTime.nanoseconds); + + printf("\n[DEBUG] aptpd2: Virtual Local Time to TSync %u nanosecondsLo, %u nanosecondsHi", + localTime.nanosecondsLo, localTime.nanosecondsHi); + + printf( + "\n[DEBUG] aptpd2: User Data Length = %u, userdata.userByte0 = %u, userdata.userByte1 = %u, userdata.userByte2 = %u", + userdata.userDataLength, userdata.userByte0, userdata.userByte1, userdata.userByte2); +#endif + + returnValue = + TSync_BusSetGlobalTime(ptpConfig.timebaseHandle, &globalTime, &userdata, &measureData, &localTime); + + if (E_NOT_OK == returnValue) { + fprintf(stderr, "[ERROR] aptpd2: Call to TSync_BusSetGlobalTime failed\n"); + } + } /* End of conditon Sync-State */ + else { + fprintf(stderr, "[ERROR] aptpd2: The Sync-State is not in FUP-RECEIVED. No BusSetGlobalTime called \n"); + } +} /* End of function Score_PtpBusSetGlobalTime */ diff --git a/src/ptpd/src/score/src/score_ptp_provider.c b/src/ptpd/src/score/src/score_ptp_provider.c new file mode 100644 index 0000000..f3509fb --- /dev/null +++ b/src/ptpd/src/score/src/score_ptp_provider.c @@ -0,0 +1,208 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include +#include + +#include "inc/score_ptp_subtlv.h" +#include "inc/score_ptp_arith.h" +#include "inc/score_ptp_common.h" + +/** @brief Consolidates the all the provider timing information to compute + * PreciseOriginTime + */ +Score_PtpProviderDataType providerData; + +/** @brief Type for storing the configuration information and information that + * is derived from the configuration. Configuration info mainly comes from the + * command line. + */ +extern Score_PtpConfigType ptpConfig; + +/** @brief Update the VirtualLocalTime for given message type. + * @details + * This functions gets the Virtual local Time from the TSync and updates it in + * the provider data structure. Dependending on the argument - Sync or FUP + * the VLT will be updated in the respective variable. These values are T0 and + * T2 for the preciseOriginTime computation. + * @param vltEgress SCORE_PTP_EGRESS_T0 for updating T0 + * SCORE_PTP_EGRESS_T4 for updating T4. + * @return None. + */ +void Score_PtpReadEgressVlt(Score_PtpEgressVltType vltEgress) { + TSync_ReturnType returnValue = E_NOT_OK; + TSync_VirtualLocalTimeType virtualLocalTime = {0u, 0u}; + + /* Get the Current Virtual Local Time */ + returnValue = TSync_GetCurrentVirtualLocalTime(ptpConfig.timebaseHandle, &virtualLocalTime); + + if (E_NOT_OK == returnValue) { + fprintf( + stderr, + "[ERROR] aptpd2: In function Score_PtpReadEgressVlt, failed to get Virtual Local Time from tsync-ptp-lib\n"); + + } else { + if (SCORE_PTP_EGRESS_T4 == vltEgress) { + providerData.t4Vlt.nanosecondsHi = virtualLocalTime.nanosecondsHi; + + providerData.t4Vlt.nanosecondsLo = virtualLocalTime.nanosecondsLo; + } + } +} /* End of function Score_PtpReadEgressVlt */ + +/** @brief Update the GlobalTime and VirtualLocalTime for T0 and T0Vlt + * respectively. + * @details + * This function gets the Virtual local Time and GlobalTime from TSync and + * store it as T0 and T0Vlt. These values are used later to compute OriginTime. + * @param None. + * @return None. + */ +void Score_PtpBusGetGlobalTime(void) { + TSync_ReturnType returnValue = E_NOT_OK; + TSync_UserDataType userdata; + TSync_MeasurementType measureData; + TSync_VirtualLocalTimeType vlt; + TSync_TimeStampType globalTime; + + memset((void *)(&userdata), 0u, sizeof(TSync_UserDataType)); + memset((void *)(&measureData), 0u, sizeof(TSync_MeasurementType)); + memset((void *)(&vlt), 0u, sizeof(TSync_VirtualLocalTimeType)); + memset((void *)(&globalTime), 0u, sizeof(TSync_TimeStampType)); + + returnValue = TSync_BusGetGlobalTime(ptpConfig.timebaseHandle, &globalTime, &userdata, &measureData, &vlt); + + if (E_NOT_OK == returnValue) { + fprintf(stderr, "[ERROR] aptpd2: Failed to get Global Time from TSync\n"); + } else { +#if PTPD_SCORE_DEBUG + printf("\n [DEBUG] aptpd2: Global Time Read from timesync: "); + printf(" %u", globalTime.secondsHi); + printf(" : %u", globalTime.seconds); + printf(" : %u", globalTime.nanoseconds); + + printf("\n [DEBUG] aptpd2: Virtual Local Time: "); + printf(" %u", vlt.nanosecondsHi); + printf(" : %u", vlt.nanosecondsLo); + + printf("\n [DEBUG] aptpd2: Userdata "); + printf(" %u", userdata.userDataLength); + printf(" %u", userdata.userByte0); + printf(" %u", userdata.userByte1); + printf(" : %u", userdata.userByte2); + +#endif + + providerData.t0GlobalTime.secondsHi = globalTime.secondsHi; + providerData.t0GlobalTime.seconds = globalTime.seconds; + providerData.t0GlobalTime.nanoseconds = globalTime.nanoseconds; + + providerData.t0Vlt.nanosecondsHi = vlt.nanosecondsHi; + providerData.t0Vlt.nanosecondsLo = vlt.nanosecondsLo; + + /* Fetch User data */ + providerData.followUpMsg.subTlv.userdata.userDataLength = userdata.userDataLength; + providerData.followUpMsg.subTlv.userdata.userByte0 = userdata.userByte0; + providerData.followUpMsg.subTlv.userdata.userByte1 = userdata.userByte1; + providerData.followUpMsg.subTlv.userdata.userByte2 = userdata.userByte2; + } +} + +/** @brief Fetch T4Vlt and compute PreciseOriginTime . + * @details + * This functions fetches T4Vlt from tsync. Then based on T4Vlt and T0Vlt + * (which is stored previously in Provider information), T0 + * is interpolated to compute the PreciseOriginTime. + * @param None. + * @return None. + */ +void Score_PtpProviderComputeOriginTime(void) { + Score_PtpSignedTimeStampType globalTimeDiff; + + memset((void *)(&globalTimeDiff.ts), 0u, sizeof(TSync_TimeStampType)); + globalTimeDiff.sign = SCORE_PTP_POSITIVE; + + Score_PtpReadEgressVlt(SCORE_PTP_EGRESS_T4); + + globalTimeDiff.ts.secondsHi = providerData.t0GlobalTime.secondsHi; + globalTimeDiff.ts.seconds = providerData.t0GlobalTime.seconds; + globalTimeDiff.ts.nanoseconds = providerData.t0GlobalTime.nanoseconds; + + Score_PtpInterpolateTime(&providerData.t4Vlt, &providerData.t0Vlt, &globalTimeDiff, &providerData.followUpMsg.preciseOriginTimestamp); + +#if PTPD_SCORE_DEBUG + printf("Interpolated time to send"); + printf(" SecondsHI: %u",providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi); + printf(" Seconds: %u", providerData.followUpMsg.preciseOriginTimestamp.ts.seconds); + printf(" Nanoseconds : %u\n", providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds); + +#endif + +} + +/** @brief Fetch PreciseOriginTime from Provider data-structure. + * @details + * This functions fetches origin time that was computed and stored in provider + * data-structure based on T4Vlt and T0Vlt + * @param originTimeOut Pointer to store the computed origin time. + * @return None. + */ +void Score_PtpWriteOriginTimeToBuffer(uint8_t *originTimeOut, uint8_t originTimeSize) { + Score_PtpTimeStampType *originTimeStamp = NULL; + + if ((NULL != originTimeOut) && (originTimeSize == sizeof(Score_PtpTimeStampType))) { + originTimeStamp = (Score_PtpTimeStampType *)(originTimeOut); + + originTimeStamp->secondsHi = providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi; + originTimeStamp->seconds = providerData.followUpMsg.preciseOriginTimestamp.ts.seconds; + originTimeStamp->nanoseconds = providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds; + } +} + +/** @brief Callback function to indicate Immediate time transmission. + * @details + * This function is called by TSync to indicate that immediate time transmission + * is needed. It sets immediateTimeTransmission variable to TRUE. This will enable the time + * transmission on the Bus in the next cycle. + * @param[in] domainId The domain id for which time has to be + * transmitted. + * @return E_OK if successful, E_NOT_OK otherwise. + */ +TSync_ReturnType Score_PtpTransmitGlobalTimeCallback(TSync_SynchronizedTimeBaseType domainId) { + TSync_ReturnType returnValue = E_NOT_OK; + + /* This condition is not really necessary, since the ptpd instance is always + run for single domain */ + if (domainId == ptpConfig.timebaseId) { + + /* New time update received from TSync. Immediately send the time + over Ethernet bus */ + providerData.immediateTimeTransmission = SCORE_PTP_TRUE; + returnValue = E_OK; + } + + return returnValue; +} + +/** @brief Returns if Immediate time transmission is set to TRUE. + * @details + * This function returns if the Immediate time transmission is set to TRUE. + * @param[in] None + * @return TRUE if Immediate time transmission is TRUE, FALSE otherwise. + */ +Score_PtpBooleanType Score_PtpIsImmediateTimeTriggerTrue(void) { + return providerData.immediateTimeTransmission; +} + +/** @brief Resets the Immediate time transmission flag to FALSE. + * @details + * This function resets the Immediate time transmission flag to FALSE. It shall + * be called as soon as the transmission is initiated. + * @param None. + * @return None. + */ +void Score_PtpResetImmediateTimeTrigger(void) { + providerData.immediateTimeTransmission = SCORE_PTP_FALSE; +} diff --git a/src/ptpd/src/score/src/score_ptp_subtlv.c b/src/ptpd/src/score/src/score_ptp_subtlv.c new file mode 100644 index 0000000..8ab9ce9 --- /dev/null +++ b/src/ptpd/src/score/src/score_ptp_subtlv.c @@ -0,0 +1,554 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "../inc/score_ptp_subtlv.h" +#include "../inc/Std_Types.h" + +#include +#include +#include "inc/score_ptp_types.h" +#include "inc/score_ptp_config.h" + +/* Stub implementation of CRC function - CRC functionality is currently not available (for more details see #94128) */ +uint8 Crc_CalculateCRC8H2F(const uint8* Crc_DataPtr, uint32 Crc_Length, uint8 Crc_StartValue8, boolean Crc_IsFirstCall) { + (void)Crc_DataPtr; + (void)Crc_Length; + (void)Crc_IsFirstCall; + return Crc_StartValue8; +} + +/** @brief Consolidates the all the provider timing information to compute + * PreciseOriginTime + */ +extern Score_PtpProviderDataType providerData; + +/** @brief The structure that is used to store the information for computation + * of globalTime */ +extern Score_PtpConsumerDataType consumerData; + +/** @brief Type for storing the configuration information and information that + * is derived from the configuration. Configuration info mainly comes from the + * command line. + */ +extern Score_PtpConfigType ptpConfig; + +/** @brief Function to generate the DataId for the given sequenceId + * @details + * This function generates the DataId for the given sequenceId. + * First the mod 16 operation is carried out on the sequenceId. Then the remainder + * is used as index to fetch the FupDataId from the configured list of values. + * @param[in] number Sequence Id for the followup message(Provider or Consumer) + * @return Calculated DataId + */ +static inline uint8_t Score_PtpGetDataId(uint16_t sequenceId) { + /* [PRS_TS_00112] The DataID shall be calculated as: DataID = DataIDList + [Follow_Up.sequenceId mod 16], where DataIDList is given by configuration + for the Follow_Up.(RS_TS_20061) */ + return ptpConfig.timebaseConfig.fupDataIdList[sequenceId % 16]; +} + +/** @brief Function to flip the bytes for 16 bit unsigned values. + * @details + * This function flips the 16bit value from host to network byte order. + * @param[in] number 16 bit unsigned number in host byte order + * @return 16 bit unsigned number in network byte order. + */ +static inline uint16_t Score_PtpFlip16(uint16_t number) { + return htons(number); +} + +/** @brief Updates the SubTlvTimeSecured information into the ptpd buffer. + * @details + * This function is used by Provider to write the TimeSecured information to + * outgoing buffer. + * @param[out] timeSecuredOut Pointer to timeSecured array. + * @return Returns number of bytes written. + */ +static uint16_t Score_PtpWriteSubTLVTimeSecured(uint8_t *timeSecuredOut) { + unsigned int index = 0u; + uint8_t dataID = 0u; + Score_PtpSubtlvTimeSecuredType timeSecured; + uint8_t crcInputStream0[SCORE_PTP_TIMESECURED_CRC_TIME_0_STREAM_LENGTH]; + uint8_t crcInputStream1[SCORE_PTP_TIMESECURED_CRC_TIME_1_STREAM_LENGTH]; + + memset(crcInputStream0, 0u, SCORE_PTP_TIMESECURED_CRC_TIME_0_STREAM_LENGTH); + memset(crcInputStream1, 0u, SCORE_PTP_TIMESECURED_CRC_TIME_1_STREAM_LENGTH); + + timeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + timeSecured.subtlvLength = SCORE_PTP_FUP_SUBTLV_LEN_FIELD_TIMESECURED; + + timeSecured.crcTimeFlags = ptpConfig.configGlobals.crcTimeFlags; + + /* Update timeflags in crc stream */ + crcInputStream0[index++] = ptpConfig.configGlobals.crcTimeFlags; + + /* Update the remaining configuration elements in crc stream */ + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK)) { + /*[PRS_TS_00184] If applying the CRC calculation on multibyte message data, the + byte order shall be in ascending order of the octets, i.e., the octet with the lowest offset + shall be used first.*/ + crcInputStream0[index++] = (ptpConfig.timebaseId & SCORE_PTP_LOWER_BYTE_MASK); + crcInputStream0[index++] = ((ptpConfig.timebaseId & SCORE_PTP_HIGHER_BYTE_MASK) >> 8); + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK)) { + memcpy(&crcInputStream0[index], &providerData.followUpMsg.followupHeaderInfo.sourcePortIdentity, + SCORE_PTP_FUP_MESSAGE_HEADER_PORT_IDENTITY_LENGTH); + index += SCORE_PTP_FUP_MESSAGE_HEADER_PORT_IDENTITY_LENGTH; + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK)) { + memcpy(&crcInputStream0[index], &providerData.followUpMsg.preciseOriginTimestamp, + SCORE_PTP_FUP_MESSAGE_PRECISE_ORIGIN_TIME_LENGTH); + index += SCORE_PTP_FUP_MESSAGE_PRECISE_ORIGIN_TIME_LENGTH; + } + /* Finally, update DataID in crc stream */ + dataID = Score_PtpGetDataId(providerData.followUpMsg.followupHeaderInfo.sequenceId); + crcInputStream0[index++] = dataID; + + /* Is the stream length always 5 or it depends on the configuration */ + timeSecured.crcTime0 = Crc_CalculateCRC8H2F((uint8_t *)(&crcInputStream0[0]), index, 0u, SCORE_PTP_TRUE); + + /* Reset the index for next stream */ + index = 0u; + + /* Update timeflags in crc stream */ + crcInputStream1[index++] = ptpConfig.configGlobals.crcTimeFlags; + + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK)) { + crcInputStream1[index++] = (providerData.followUpMsg.followupHeaderInfo.messageLength & SCORE_PTP_LOWER_BYTE_MASK); + crcInputStream1[index++] = + ((providerData.followUpMsg.followupHeaderInfo.messageLength & SCORE_PTP_HIGHER_BYTE_MASK) >> 8); + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK)) { + memcpy(&crcInputStream1[index], &providerData.followUpMsg.followupHeaderInfo.correctionField, + SCORE_PTP_FUP_MESSAGE_HEADER_CORRECTION_FIELD_LENGTH); + index += SCORE_PTP_FUP_MESSAGE_HEADER_CORRECTION_FIELD_LENGTH; + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK)) { + crcInputStream1[index++] = (providerData.followUpMsg.followupHeaderInfo.sequenceId & SCORE_PTP_LOWER_BYTE_MASK); + crcInputStream1[index++] = + ((providerData.followUpMsg.followupHeaderInfo.sequenceId & SCORE_PTP_HIGHER_BYTE_MASK) >> 8); + } + + /* Finally, update DataID in crc stream */ + crcInputStream1[index++] = dataID; + + timeSecured.crcTime1 = + Crc_CalculateCRC8H2F((uint8_t *)(&crcInputStream1[0]), (uint8_t)(index), 0u, SCORE_PTP_TRUE); + + memcpy((void *)(timeSecuredOut), (void *)(&timeSecured), sizeof(Score_PtpSubtlvTimeSecuredType)); + return ((uint16_t)(sizeof(Score_PtpSubtlvTimeSecuredType))); +} + +/** @brief Updates the SubTlv Userdata information into the ptpd buffer. + * @details + * This function is used by Provider to write the Userdata information to + * outgoing buffer. + * @param[out] userdataOut Pointer to userdata array. + * @return Returns number of bytes written. + */ +static uint16_t Score_PtpWriteSubTLVUserdata(uint8_t *userdataOut) { + Score_PtpSubtlvUserdataType userdata; + uint8_t crcInputStream[SCORE_PTP_USERDATA_CRC_STREAM_LENGTH]; + memset(crcInputStream, 0u, SCORE_PTP_USERDATA_CRC_STREAM_LENGTH); + + /* Length of the Subtlv type - User data length */ + userdata.userdataLen = providerData.followUpMsg.subTlv.userdata.userDataLength; + + /* User data byte 0 */ + userdata.userdataByte0 = providerData.followUpMsg.subTlv.userdata.userByte0; + + /* User data byte 1 */ + userdata.userdataByte1 = providerData.followUpMsg.subTlv.userdata.userByte1; + + /* User data byte 2 */ + userdata.userdataByte2 = providerData.followUpMsg.subTlv.userdata.userByte2; + + if (TSYNC_CRC_SUPPORTED == ptpConfig.timebaseConfig.crcSupport) { + /* SubTLV type */ + userdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + + crcInputStream[0] = userdata.userdataLen; + crcInputStream[1] = userdata.userdataByte0; + crcInputStream[2] = userdata.userdataByte1; + crcInputStream[3] = userdata.userdataByte2; + + if (ptpConfig.timebaseConfig.numFupDataIdEntries > 0u) { + crcInputStream[4] = Score_PtpGetDataId(providerData.followUpMsg.followupHeaderInfo.sequenceId); + } + userdata.crcUserdata = Crc_CalculateCRC8H2F((uint8_t *)(&crcInputStream[0]), SCORE_PTP_USERDATA_CRC_STREAM_LENGTH, + 0u, SCORE_PTP_TRUE); + } else { + /* SubTLV type */ + userdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC; + + /* Reserved field - CRC not supported */ + userdata.crcUserdata = 0u; + } + + /* SubTLV length */ + userdata.subtlvLength = SCORE_PTP_FUP_SUBTLV_LEN_FIELD_USERDATA; + + memcpy((void *)(userdataOut), (void *)(&userdata), sizeof(Score_PtpSubtlvUserdataType)); + return ((uint16_t)(sizeof(Score_PtpSubtlvUserdataType))); +} + +/** @brief Updates the Autosar TLV information into the ptpd buffer. + * @details + * This function is used by Provider to write the Autosar TLV information to + * outgoing buffer. + * @param[out] autosarTlvBufferOut Pointer to buffer where Autosar TLV can + * be updated. + * @return None. + */ +void Score_PtpWriteAutosarTlvToBuffer(uint8_t *autosarTlvBufferOut) { + Score_PtpAutosarTlvType autosarTlv; + Score_PtpAutosarTlvType *autosarTlvPtr = &autosarTlv; + uint16_t tlvLength = 0u; + uint8_t *subTLVPtr = NULL; + + /* Include SubTLV in outgoing buffer only if message compliance is TSYNC_MC_IEEE8021AS_AUTOSAR */ + if (TSYNC_MC_IEEE8021AS_AUTOSAR == ptpConfig.timebaseConfig.messageCompliance) { + if (NULL != autosarTlvBufferOut) { + /* Get the place */ + subTLVPtr = autosarTlvBufferOut + SCORE_PTP_FUP_AUTOSAR_TLV_INFO_LENGTH; + + /* Update information for TimeSecured SubTLV only if crcSupport is enabled */ + if ((TSYNC_CRC_SUPPORTED == ptpConfig.timebaseConfig.crcSupport) && + (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV)) { + tlvLength = Score_PtpWriteSubTLVTimeSecured(subTLVPtr); + subTLVPtr += SCORE_PTP_FUP_SUBTLV_TIMESECURED_LENGTH; + } + + /* Update information for Status SubTLV */ + if (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV) { + /* Write Status subtlv to the buffer - Todo */ + subTLVPtr += SCORE_PTP_FUP_SUBTLV_STATUS_LENGTH; + } + + /* Update information for Userdata SubTLV */ + if (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV) { + tlvLength = (uint16_t)(tlvLength + Score_PtpWriteSubTLVUserdata(subTLVPtr)); + subTLVPtr += SCORE_PTP_FUP_SUBTLV_USERDATA_LENGTH; + } + + /* AutosarTlvType */ + autosarTlvPtr->tlvType = Score_PtpFlip16((uint16_t)(SCORE_PTP_FUP_AUTOSAR_TLV_TYPE)); + + /* AUTOSAR OrganizationId 0x0080C2 [IEEE802.1AS] */ + autosarTlvPtr->orgId[0] = (SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID >> 16) & 0xFF; + autosarTlvPtr->orgId[1] = (SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID >> 8) & 0xFF; + autosarTlvPtr->orgId[2] = SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID & 0xFF; + + /* AUTOSAR organizationSub-Type */ + autosarTlvPtr->orgSubType[0] = (SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE >> 16) & 0xFF; + autosarTlvPtr->orgSubType[1] = (SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE >> 8) & 0xFF; + autosarTlvPtr->orgSubType[2] = SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE & 0xFF; + + /* AUTOSAR length field = 6 + totalSubtlvLength */ + tlvLength = (uint16_t)(tlvLength + (uint16_t)(SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID_LENGTH) + + (uint16_t)(SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE_LENGTH)); + + autosarTlvPtr->tlvLength = Score_PtpFlip16(tlvLength); + + memcpy((void *)autosarTlvBufferOut, (void *)autosarTlvPtr, (size_t)SCORE_PTP_FUP_AUTOSAR_TLV_INFO_LENGTH); + } + } +} + +/** @brief Read the SubTlv TimeSecured information from the ptpd buffer. + * @details + * This function is used by Consumer to read the TimeSecured information from + * incoming buffer. + * @param[out] timeSecuredIn Pointer to timeSecured array. + * @return int Number of bytes read, -1 otherwise. + */ +static int Score_PtpReadSubTLVTimeSecured(const uint8_t *timeSecuredIn) { + Score_PtpSubtlvTimeSecuredType timeSecured; + unsigned int index = 0u; + uint8_t dataID = 0u; + uint8_t crcComputedTime0 = 0u; + uint8_t crcComputedTime1 = 0u; + uint8_t crcInputStream0[SCORE_PTP_TIMESECURED_CRC_TIME_0_STREAM_LENGTH]; + uint8_t crcInputStream1[SCORE_PTP_TIMESECURED_CRC_TIME_1_STREAM_LENGTH]; + uint8_t processPacket; + + memcpy((void *)(&timeSecured), (void *)(timeSecuredIn), sizeof(Score_PtpSubtlvTimeSecuredType)); + memset(crcInputStream0, 0u, SCORE_PTP_TIMESECURED_CRC_TIME_0_STREAM_LENGTH); + memset(crcInputStream1, 0u, SCORE_PTP_TIMESECURED_CRC_TIME_1_STREAM_LENGTH); + + if (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV) { + if (TSYNC_CRC_NOT_VALIDATED != ptpConfig.timebaseConfig.crcValidation) { + if (SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED == timeSecured.subtlvType) { + /* Update timeflags in crc stream */ + crcInputStream0[index++] = timeSecured.crcTimeFlags; + + /* Update the remaining configuration elements in crc stream */ + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK)) { + /*[PRS_TS_00184] If applying the CRC calculation on multibyte message data, the + byte order shall be in ascending order of the octets, i.e., the octet with the lowest offset + shall be used first.*/ + crcInputStream0[index++] = (ptpConfig.timebaseId & SCORE_PTP_LOWER_BYTE_MASK); + crcInputStream0[index++] = ((ptpConfig.timebaseId & SCORE_PTP_HIGHER_BYTE_MASK) >> 8); + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK)) { + memcpy(&crcInputStream0[index], &consumerData.followUpMsg.followupHeaderInfo.sourcePortIdentity, + SCORE_PTP_FUP_MESSAGE_HEADER_PORT_IDENTITY_LENGTH); + index += SCORE_PTP_FUP_MESSAGE_HEADER_PORT_IDENTITY_LENGTH; + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK)) { + memcpy(&crcInputStream0[index], &consumerData.followUpMsg.preciseOriginTimestamp, + SCORE_PTP_FUP_MESSAGE_PRECISE_ORIGIN_TIME_LENGTH); + index += SCORE_PTP_FUP_MESSAGE_PRECISE_ORIGIN_TIME_LENGTH; + } + /* Finally, update DataID in crc stream */ + dataID = Score_PtpGetDataId(consumerData.followUpMsg.followupHeaderInfo.sequenceId); + crcInputStream0[index++] = dataID; + + /* Compute crc_time_0 now */ + /* Is the stream length always 5 or it depends on the configuration - currentlly it is taken based on + * configuration */ + crcComputedTime0 = + Crc_CalculateCRC8H2F((uint8_t *)(&crcInputStream0[0]), index, 0u, SCORE_PTP_TRUE); + + /* Reset the index for next stream */ + index = 0u; + + /* Update timeflags in crc stream */ + crcInputStream1[index++] = timeSecured.crcTimeFlags; + + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK)) { + crcInputStream1[index++] = + (consumerData.followUpMsg.followupHeaderInfo.messageLength & SCORE_PTP_LOWER_BYTE_MASK); + crcInputStream1[index++] = + ((consumerData.followUpMsg.followupHeaderInfo.messageLength & SCORE_PTP_HIGHER_BYTE_MASK) >> 8); + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK)) { + memcpy(&crcInputStream1[index], &consumerData.followUpMsg.followupHeaderInfo.correctionField, + SCORE_PTP_FUP_MESSAGE_HEADER_CORRECTION_FIELD_LENGTH); + index += SCORE_PTP_FUP_MESSAGE_HEADER_CORRECTION_FIELD_LENGTH; + } + if (SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK)) { + crcInputStream1[index++] = + (consumerData.followUpMsg.followupHeaderInfo.sequenceId & SCORE_PTP_LOWER_BYTE_MASK); + crcInputStream1[index++] = + ((consumerData.followUpMsg.followupHeaderInfo.sequenceId & SCORE_PTP_HIGHER_BYTE_MASK) >> 8); + } + + /* Finally, update DataID in crc stream */ + crcInputStream1[index++] = dataID; + + /* Compute crc_time_1 now */ + /* Is the stream length always 5 or it depends on the configuration - currentlly it is taken based on + * configuration */ + crcComputedTime1 = + Crc_CalculateCRC8H2F((uint8_t *)(&crcInputStream1[0]), index, 0u, SCORE_PTP_TRUE); + + if ((crcComputedTime0 == timeSecured.crcTime0) && (crcComputedTime1 == timeSecured.crcTime1)) { + processPacket = SCORE_PTP_TRUE; + } else { + processPacket = SCORE_PTP_FALSE; + printf( + "\n[INFO] score_aptpd2 : SubTLV TimeSecured has CRC inconsistencies [Incorrect CRC_Time_0 or CRC_Time_1].\n"); + } + + } else { + processPacket = SCORE_PTP_FALSE; + printf( + "[WARNING] score_aptpd2 : SubTLV TimeSecured has incorrect TLVType(%X). SubTLV TimeSecured dropped. \n", timeSecured.subtlvType); + } + } else { + processPacket = SCORE_PTP_FALSE; + printf( + "[WARNING] score_aptpd2 : SubTLV TimeSecured is configured but the configuration parameter 'crcValidation' is not enabled. Please enable crcValidation inorder to receive TimeSecured SubTLV. \n"); + } + + /* Process packet only if preconditions are met */ + if (SCORE_PTP_TRUE == processPacket) { + return sizeof(Score_PtpSubtlvTimeSecuredType); + } else { + return -1; + } + } else { + /* Skip(Seeking) the bytes if the timesecured is present in the packet */ + if (SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED == timeSecured.subtlvType) { + return sizeof(Score_PtpSubtlvTimeSecuredType); + } + else { + return 0; + } + } +} + +/** @brief Read the SubTLV userdata information from the ptpd buffer. + * @details + * This function is used by Consumer to read the userdata information from + * incoming buffer. + * @param[out] userdataIn Pointer to userdata array. + * @return int Number of bytes read, -1 otherwise. + */ +static int Score_PtpReadSubTLVUserdata(const uint8_t *userdataIn) { + Score_PtpSubtlvUserdataType userdata; + uint8_t crcInputStream[SCORE_PTP_USERDATA_CRC_STREAM_LENGTH]; + uint8_t crcComputed = 0u; + uint8_t processPacket; + + memcpy((void *)(&userdata), (void *)(userdataIn), sizeof(Score_PtpSubtlvUserdataType)); + memset(crcInputStream, 0u, SCORE_PTP_USERDATA_CRC_STREAM_LENGTH); + + if (TSYNC_SUBTLV_SUPPORTED == ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV) { + if (SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC == userdata.subtlvType) { + /* TSYNC_CRC_NOT_VALIDATED - CRC enabled follow-up messages must to be discarded */ + if (TSYNC_CRC_NOT_VALIDATED == ptpConfig.timebaseConfig.crcValidation) { + processPacket = SCORE_PTP_FALSE; + } + /* TSYNC_CRC_IGNORED - Dont care for the CRC Validation, just process the SubTLV */ + else if (TSYNC_CRC_IGNORED == ptpConfig.timebaseConfig.crcValidation) { + processPacket = SCORE_PTP_TRUE; + } + /* TSYNC_CRC_OPTIONAL - Go for CRC Validation as the packet received is CRC enabled + TSYNC_CRC_VALIDATED - CRC Validation is mandatory */ + else if ((TSYNC_CRC_OPTIONAL == ptpConfig.timebaseConfig.crcValidation) || + (TSYNC_CRC_VALIDATED == ptpConfig.timebaseConfig.crcValidation)) { + crcInputStream[0] = userdata.userdataLen; + crcInputStream[1] = userdata.userdataByte0; + crcInputStream[2] = userdata.userdataByte1; + crcInputStream[3] = userdata.userdataByte2; + if (ptpConfig.timebaseConfig.numFupDataIdEntries > 0u) { + crcInputStream[4] = Score_PtpGetDataId(consumerData.followUpMsg.followupHeaderInfo.sequenceId); + } + + crcComputed = Crc_CalculateCRC8H2F((uint8_t *)(&crcInputStream[0]), SCORE_PTP_USERDATA_CRC_STREAM_LENGTH, + 0u, SCORE_PTP_TRUE); + + if (crcComputed == userdata.crcUserdata) { + processPacket = SCORE_PTP_TRUE; + } else { + processPacket = SCORE_PTP_FALSE; + printf("\n[INFO] score_aptpd2 : SubTLV Userdata has CRC inconsistencies [Incorrect CRC].\n"); + } + } else { + processPacket = SCORE_PTP_FALSE; + printf("[INFO] score_aptpd2 : Unknown crcValidation [%d] configured for the domain-id [%d]\n", + ptpConfig.timebaseConfig.crcValidation, ptpConfig.timebaseId); + } + } else if (SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC == userdata.subtlvType) { + if (TSYNC_CRC_VALIDATED == ptpConfig.timebaseConfig.crcValidation) { + processPacket = SCORE_PTP_FALSE; + } else { + processPacket = SCORE_PTP_TRUE; + } + } else { + processPacket = SCORE_PTP_FALSE; + printf("[WARNING] score_aptpd2 : SubTLV Userdata has incorrect TLVType(%X). SubTLV Userdata dropped. \n", + userdata.subtlvType); + } + + if (SCORE_PTP_TRUE == processPacket) { + consumerData.followUpMsg.subTlv.userdata.userDataLength = userdata.userdataLen; + consumerData.followUpMsg.subTlv.userdata.userByte0 = userdata.userdataByte0; + consumerData.followUpMsg.subTlv.userdata.userByte1 = userdata.userdataByte1; + consumerData.followUpMsg.subTlv.userdata.userByte2 = userdata.userdataByte2; + + return sizeof(Score_PtpSubtlvUserdataType); + } else { + return -1; + } + } else { + /* Skip(Seeking) the bytes if the userdata is present in the packet */ + if ((SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC == userdata.subtlvType) || + (SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC == userdata.subtlvType)) { + return sizeof(Score_PtpSubtlvUserdataType); + } + else { + return 0; + } + } +} + +/** @brief Read the SubTLV status information from the ptpd buffer. + * @details + * This function is used by Consumer to read the status information from + * incoming buffer. + * @param[out] statusSubTlvIn Pointer to status array. + * @return int Number of bytes read, -1 otherwise. + */ +static int Score_PtpReadSubTLVStatus(const uint8_t *statusSubTlvIn) { + + Score_PtpSubtlvStatusType statusSubTlv; + + memset((void *)(&statusSubTlv), 0u, sizeof(Score_PtpSubtlvStatusType)); + memcpy((void *)(&statusSubTlv), (void *)(statusSubTlvIn), sizeof(Score_PtpSubtlvStatusType)); + + /* Gateway scenario is not yet supported by Adaptive Autosar. + Since Status SubTLV covers gateway scenario, we ignore any StatusTLV that is received */ + if ((SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC == statusSubTlv.subtlvType) || (SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITHOUT_CRC == statusSubTlv.subtlvType )) { + printf("[INFO] score_aptpd2 : Received a unsupported SubTLV - Status.Ignoring the contents of the SubTlv and processing the followup message.\n"); + return sizeof(Score_PtpSubtlvStatusType); + } + + return 0; +} + +/** @brief Reads the Autosar TLV information from the ptpd buffer. + * @details + * This function is used by Consumer to read/extract the TLV from the + * incoming buffer. + * @param[in] autosarTlvBufferIn Pointer to buffer where Autosar Tlv information + * can be read from. + * @return TRUE if Autosar SubTLVs were read successfully, FALSE otherwise + */ +Score_PtpBooleanType Score_PtpReadSubTlvFromBuffer(const uint8_t *autosarTlvBufferIn) { + Score_PtpAutosarTlvType *autosarTlvPtr = NULL; + const uint8_t *subTLVPtr = NULL; + int subTlvLength = 0; + uint16_t expectedLenField = (uint16_t)(ptpConfig.configGlobals.configSubTlvLength + (uint16_t)(SCORE_PTP_FUP_AUTOSAR_TLV_ORGAID_LENGTH) + + (uint16_t)(SCORE_PTP_FUP_AUTOSAR_TLV_ORGSUBTYPE_LENGTH)); + + /* Read SubTLV from incoming buffer only if message compliance is TSYNC_MC_IEEE8021AS_AUTOSAR */ + if (TSYNC_MC_IEEE8021AS_AUTOSAR == ptpConfig.timebaseConfig.messageCompliance) { + if (NULL != autosarTlvBufferIn) { + autosarTlvPtr = (Score_PtpAutosarTlvType *)(autosarTlvBufferIn); + + /* Check if any SubTLVs are configured */ + if (0u < ptpConfig.configGlobals.configSubTlvLength) { + /* We are expecting ATLEAST our configured SubTLVs to be available in the followup packet */ + if (expectedLenField <= Score_PtpFlip16(autosarTlvPtr->tlvLength)) { + subTLVPtr = autosarTlvBufferIn + SCORE_PTP_FUP_AUTOSAR_TLV_INFO_LENGTH; + /* Update information for TimeSecured SubTLV */ + subTlvLength = Score_PtpReadSubTLVTimeSecured(subTLVPtr); + + if (0 <= subTlvLength) { + subTLVPtr += subTlvLength; + } else { + /* Entire Follow-up message must be dropped when CRC validation(within SubTLVs) was not + * successful */ + return SCORE_PTP_FALSE; + } + + /* We dont support status SubTLV, this only serves as default handling */ + subTLVPtr += Score_PtpReadSubTLVStatus(subTLVPtr); + + /* Update information for Userdata SubTLV */ + subTlvLength = Score_PtpReadSubTLVUserdata(subTLVPtr); + if (0 <= subTlvLength) { + subTLVPtr += subTlvLength; + } else { + /* Entire Follow-up message must be dropped when CRC validation(within SubTLVs) was not + * successful */ + return SCORE_PTP_FALSE; + } + } else { + printf( + "[WARNING] aptpd2: Dropped the autosar TLV as the SubTLVs received do not match the configured SubTLVs.\n"); + return SCORE_PTP_FALSE; + } + } else { + /* The consumer has no SubTLV configured. Proceed further to process only origin time */ + return SCORE_PTP_TRUE; + } + } + } + /* Control reaches here only when the subtlv parsing is successful. Hence packet does not get dropped */ + return SCORE_PTP_TRUE; +} diff --git a/src/ptpd/src/signaling.c b/src/ptpd/src/signaling.c new file mode 100644 index 0000000..e8942b5 --- /dev/null +++ b/src/ptpd/src/signaling.c @@ -0,0 +1,1391 @@ +/******************************************************************************** + * Modifications (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ +/*- + * Copyright (c) 2015 Wojciech Owczarek + * Copyright (c) 2014 Perseus Telecom + * + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file signaling.c + * @date Mon Jan 14 00:40:12 GMT 2014 + * + * @brief Routines to handle unicast negotiation and processing of signaling messages + * + * + */ + +#include "ptpd.h" + +/* how many times we send a cancel before we stop waiting for ack */ +#define GRANT_CANCEL_ACK_TIMEOUT 3 +/* every N grant refreshes, we re-request messages we don't seem to be receiving */ +#define GRANT_KEEPALIVE_INTERVAL 5 +/* maximum number of missed messages of given type before we re-request */ +#define GRANT_MAX_MISSED 10 + +static void updateUnicastIndex(UnicastGrantTable *table, UnicastGrantIndex *index); +static UnicastGrantTable* lookupUnicastIndex(PortIdentity *portIdentity, Integer32 transportAddress, UnicastGrantIndex *index); +static int msgIndex(Enumeration8 messageType); +static Enumeration8 msgXedni(int messageIndex); +static void initOutgoingMsgSignaling(PortIdentity* targetPortIdentity, MsgSignaling* outgoing, PtpClock *ptpClock); +static void handleSMRequestUnicastTransmission(MsgSignaling* incoming, MsgSignaling* outgoing, Integer32 sourceAddress, const RunTimeOpts *rtOpts, PtpClock *ptpClock); +static void handleSMGrantUnicastTransmission(MsgSignaling* incoming, Integer32 sourceAddress, UnicastGrantTable *grantTable, int nodeCount, PtpClock *ptpClock); +static Boolean handleSMCancelUnicastTransmission(MsgSignaling* incoming, MsgSignaling* outgoing, Integer32 sourceAddress, PtpClock* ptpClock); +static void handleSMAcknowledgeCancelUnicastTransmission(MsgSignaling* incoming, Integer32 sourceAddress, PtpClock* ptpClock); +static Boolean prepareSMRequestUnicastTransmission(MsgSignaling* outgoing, UnicastGrantData *grant, PtpClock* ptpClock); +static Boolean prepareSMCancelUnicastTransmission(MsgSignaling* outgoing, UnicastGrantData* grant, PtpClock* ptpClock); +static void requestUnicastTransmission(UnicastGrantData *grant, UInteger32 duration, const RunTimeOpts* rtOpts, PtpClock* ptpClock); +static void issueSignaling(MsgSignaling *outgoing, Integer32 destination, const const RunTimeOpts *rtOpts, PtpClock *ptpclock); +static void cancelNodeGrants(UnicastGrantTable *nodeTable, const RunTimeOpts *rtOpts, PtpClock *ptpClock); + +/* Return unicast grant array index for given message type */ +int +msgIndex(Enumeration8 messageType) +{ + + switch(messageType) { + + case ANNOUNCE: + return ANNOUNCE_INDEXED; + case SYNC: + return SYNC_INDEXED; + case DELAY_RESP: + return DELAY_RESP_INDEXED; + case PDELAY_RESP: + return PDELAY_RESP_INDEXED; + case SIGNALING: + return SIGNALING_INDEXED; + default: + return -1; + + } + +} +/* xedni yarra ot gnidnopserroc epyt egassem PTP eht nruteR */ +static Enumeration8 +msgXedni(int messageIndex) +{ + switch(messageIndex) { + case ANNOUNCE_INDEXED: + return ANNOUNCE; + case SYNC_INDEXED: + return SYNC; + case DELAY_RESP_INDEXED: + return DELAY_RESP; + case PDELAY_RESP_INDEXED: + return PDELAY_RESP; + case SIGNALING_INDEXED: + return SIGNALING; + default: + return 0xFF; + } + +} + +/* update index table */ +static void +updateUnicastIndex(UnicastGrantTable *table, UnicastGrantIndex *index) +{ + uint32_t hash = fnvHash(&table->portIdentity, sizeof(PortIdentity), UNICAST_MAX_DESTINATIONS); + + /* peer table normally has one entry: if we got here, we might pollute the main index */ + if(!table->isPeer && index != NULL) { + index->data[hash] = table; + } + +} + +/* return matching entry from index table */ +static UnicastGrantTable* +lookupUnicastIndex(PortIdentity *portIdentity, Integer32 transportAddress, UnicastGrantIndex *index) +{ + + uint32_t hash = fnvHash((void*)portIdentity, sizeof(PortIdentity), UNICAST_MAX_DESTINATIONS); + + UnicastGrantTable* table; + + if(index == NULL) { + return NULL; + } + + table = index->data[hash]; + + if(table == NULL) { + DBG("lookupUnicastIndex: empty hit\n"); + return NULL; + } + + if(!cmpPortIdentity(portIdentity, &table->portIdentity)) { + DBG("lookupUnicastIndex: cache hit\n"); + return table; + } else { + /* hash collision */ + DBG("lookupUnicastIndex: hash collision\n"); + return NULL; + } + +} + + +/* find which grant table entry the given port belongs to: + - if not found, return first free entry, store portID and/or address + - if found, find the entry it belongs to + - look up the index table, only iterate on miss + - if update is FALSE, only a search is performed +*/ +UnicastGrantTable* +findUnicastGrants +(const PortIdentity* portIdentity, Integer32 transportAddress, UnicastGrantTable *grantTable, UnicastGrantIndex *index, int nodeCount, Boolean update) +{ + + int i; + + UnicastGrantTable *found = NULL; + UnicastGrantTable *firstFree = NULL; + + UnicastGrantTable *nodeTable; + + PortIdentity tmpIdentity = *portIdentity; + + /* look up the index table*/ + if(index != NULL) { + tmpIdentity.portNumber |= index->portMask; + found = lookupUnicastIndex(&tmpIdentity, transportAddress, index); + } + + if(found != NULL) { + DBG("findUnicastGrants: cache hit\n"); + /* do not overwrite address if zero given + * (used by slave to preserve configured master addresses) + */ + if(update && transportAddress) { + found->transportAddress = transportAddress; + } + if(update) { + found->portIdentity = tmpIdentity; + } + + if(update) { + updateUnicastIndex(found, index); + } + + } else for(i=0; i < nodeCount; i++) { + + nodeTable = &grantTable[i]; + + /* first free entry */ + if(firstFree == NULL) { + if(portIdentityEmpty(&nodeTable->portIdentity) || + (nodeTable->timeLeft == 0)) { + firstFree = nodeTable; + } + } + + /* port identity matches */ + if(!cmpPortIdentity((const PortIdentity*)&tmpIdentity, &nodeTable->portIdentity)) { + + found = nodeTable; + + /* do not overwrite address if zero given + * (used by slave to preserve configured master addresses) + */ + + if(update && transportAddress) { + found->transportAddress = transportAddress; + } + + if(update) { + found->portIdentity = tmpIdentity; + } + + DBG("findUnicastGrants: cache miss - %d iterations %d\n", i); + + if(update) { + updateUnicastIndex(found, index); + } + + break; + + } + + /* no port identity match but we have a transport address match */ + if(nodeTable->transportAddress && + (nodeTable->transportAddress==transportAddress)) { + + found = nodeTable; + if(update) { + found->portIdentity = tmpIdentity; + updateUnicastIndex(found, index); + } + break; + } + + } + + if(found != NULL) { + return found; + } + + /* will return NULL if there are no free slots, otherwise the first free slot */ + if(update && firstFree != NULL) { + firstFree->portIdentity = tmpIdentity; + firstFree->transportAddress = transportAddress; + updateUnicastIndex(firstFree, index); + /* new set of grants - reset sequence numbers */ + for(i=0; i < PTP_MAX_MESSAGE_INDEXED; i++) { + firstFree->grantData[i].sentSeqId = 0; + } + + /* - You know, we could as well have sex now. + * - Yes, dear, but, let's not. + */ + /* memset(firstFree->grantData,0,PTP_MAX_MESSAGE * sizeof(UnicastGrantData)); */ + } + + return firstFree; + +} + + +/**\brief Initialise outgoing signaling message fields*/ +static void +initOutgoingMsgSignaling(PortIdentity* targetPortIdentity, MsgSignaling* outgoing, PtpClock *ptpClock) +{ + /* set header fields */ + outgoing->header.transportSpecific = ptpClock->portDS.transportSpecific; + outgoing->header.messageType = SIGNALING; + outgoing->header.versionPTP = ptpClock->portDS.versionNumber; + outgoing->header.domainNumber = ptpClock->defaultDS.domainNumber; + /* set header flagField to zero for management messages, Spec 13.3.2.6 */ + outgoing->header.flagField0 = 0x00; + outgoing->header.flagField1 = 0x00; + outgoing->header.correctionField.msb = 0; + outgoing->header.correctionField.lsb = 0; + copyPortIdentity(&outgoing->header.sourcePortIdentity, &ptpClock->portDS.portIdentity); + + outgoing->header.sequenceId = ptpClock->sentSignalingSequenceId++; + + outgoing->header.controlField = 0x5; /* deprecrated for ptp version 2 */ + outgoing->header.logMessageInterval = 0x7F; + + /* set management message fields */ + copyPortIdentity( &outgoing->targetPortIdentity, targetPortIdentity); + + /* init managementTLV */ + XMALLOC(outgoing->tlv, sizeof(SignalingTLV)); + outgoing->tlv->valueField = NULL; + outgoing->tlv->lengthField = 0; +} + + + +/**\brief Handle incoming REQUEST_UNICAST_TRANSMISSION signaling TLV type*/ +static void +handleSMRequestUnicastTransmission(MsgSignaling* incoming, MsgSignaling* outgoing, Integer32 sourceAddress, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + char portId[PATH_MAX]; + UnicastGrantData *myGrant; + UnicastGrantTable *nodeTable; + Boolean granted = TRUE; + SMRequestUnicastTransmission* requestData = (SMRequestUnicastTransmission*)incoming->tlv->valueField; + SMGrantUnicastTransmission* grantData = NULL; + Enumeration8 messageType = requestData->messageType; +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + struct in_addr tmpAddr; + tmpAddr.s_addr = sourceAddress; +#endif /* RUNTIME_DEBUG */ + snprint_PortIdentity(portId, PATH_MAX, &incoming->header.sourcePortIdentity); + + initOutgoingMsgSignaling(&incoming->header.sourcePortIdentity, outgoing, ptpClock); + XMALLOC(outgoing->tlv->valueField, sizeof(SMGrantUnicastTransmission)); + grantData = (SMGrantUnicastTransmission*)outgoing->tlv->valueField; + + outgoing->header.flagField0 |= PTP_UNICAST; + outgoing->tlv->tlvType = TLV_GRANT_UNICAST_TRANSMISSION; + outgoing->tlv->lengthField = 8; + + + DBG("Received REQUEST_UNICAST_TRANSMISSION message for message %s from %s(%s) - duration %d interval %d\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr), requestData->durationField, + requestData->logInterMessagePeriod); + + nodeTable = findUnicastGrants(&incoming->header.sourcePortIdentity, sourceAddress, ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, TRUE); + + if(nodeTable == NULL) { + if(ptpClock->slaveCount >= UNICAST_MAX_DESTINATIONS) { + DBG("REQUEST_UNICAST_TRANSMISSION (%s): did not find node in slave table : %s (%s) - table full\n", getMessageTypeName(messageType), + inet_ntoa(tmpAddr),portId); + } else { + DBG("REQUEST_UNICAST_TRANSMISSION (%s): did not find node in slave table: %s (%s)\n", getMessageTypeName(messageType), + inet_ntoa(tmpAddr),portId); + } + /* fill the response with basic fields (namely messageType so the deny is valid */ + goto finaliseResponse; + } + + if(msgIndex(messageType) < 0) { + DBG("Received unicast request from %s for unsupported message type %d\n", inet_ntoa(tmpAddr), messageType); + goto finaliseResponse; + } + + myGrant = &nodeTable->grantData[msgIndex(messageType)]; + + myGrant->requested = TRUE; + + /* We assume the request is denied */ + grantData->renewal_invited = 0; + grantData->durationField = 0; + + ptpClock->counters.unicastGrantsRequested++; + + if(!myGrant->requestable) { + DBG("denied unicast transmission request for non-requestable message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + myGrant->granted = FALSE; + } + + if(!requestData->durationField) { + DBG("denied unicast transmission request for zero duration - message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + granted = FALSE; + } + + if(requestData->logInterMessagePeriod < myGrant->logMinInterval) { + DBG("denied unicast transmission request for too short interval - message %s from %s(%s), interval %d\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr), requestData->logInterMessagePeriod); + granted = FALSE; + } + + + if (granted) { + + /* do not deny if requested longer interval than supported - just offer the longest */ + if(requestData->logInterMessagePeriod > myGrant->logMaxInterval) { + grantData->logInterMessagePeriod = myGrant->logMaxInterval; + } else { + grantData->logInterMessagePeriod = requestData->logInterMessagePeriod; + } + + /* only offer up to the maximum duration configured, but preserve a 30 second minimum */ + if(requestData->durationField > rtOpts->unicastGrantDuration) { + grantData->durationField = rtOpts->unicastGrantDuration; + } else if(requestData->durationField <= 30) { + grantData->durationField = 30; + } else { + grantData->durationField = requestData->durationField; + } + + DBG("granted unicast transmission request - message %s to %s(%s), interval %d, duration %d s\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr), grantData->logInterMessagePeriod, + grantData->durationField); + + myGrant->duration = grantData->durationField; + /* NEW! 5 seconds for free! Why 5? refreshUnicastGrants expires the grant when it's 5 */ + myGrant->timeLeft = grantData->durationField + 10; + + /* do not reset the counter if this is being re-requested */ + if(!myGrant->granted || (myGrant->logInterval != grantData->logInterMessagePeriod)) { + myGrant->intervalCounter = 0; + } + + ptpClock->counters.unicastGrantsGranted++; + + myGrant->granted = TRUE; + myGrant->canceled = FALSE; + myGrant->cancelCount = 0; + myGrant->logInterval = grantData->logInterMessagePeriod; + + /* this could be the very first grant for this node - update node's timeLeft so it's not seen as free anymore */ + if(nodeTable->timeLeft <= 0) { + /* + 10 seconds for a grace period */ + nodeTable->timeLeft = myGrant->timeLeft + 10; + } + + /* If we've granted once, we're likely to grant again */ + grantData->renewal_invited = 1; + + outgoing->header.sequenceId = myGrant->parent->grantData[SIGNALING_INDEXED].sentSeqId; + myGrant->parent->grantData[SIGNALING_INDEXED].sentSeqId++; + + } else { + ptpClock->counters.unicastGrantsDenied++; + } + + /* Testing only */ + /* grantData->logInterMessagePeriod = requestData->logInterMessagePeriod; */ + /* grantData->durationField = requestData->durationField; */ + +finaliseResponse: + grantData->messageType = requestData->messageType; + grantData->reserved0 = 0; + grantData->reserved1 = 0; +} + +/**\brief Handle incoming GRANT_UNICAST_TRANSMISSION signaling message type*/ +static void +handleSMGrantUnicastTransmission(MsgSignaling* incoming, Integer32 sourceAddress, UnicastGrantTable *grantTable, int nodeCount, PtpClock *ptpClock) +{ + + char portId[PATH_MAX]; + SMGrantUnicastTransmission *incomingGrant = (SMGrantUnicastTransmission*)incoming->tlv->valueField; + UnicastGrantData *myGrant; + Enumeration8 messageType = incomingGrant->messageType; + UnicastGrantTable *nodeTable; +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + struct in_addr tmpAddr; + tmpAddr.s_addr = sourceAddress; +#endif /* RUNTIME_DEBUG */ + + snprint_PortIdentity(portId, PATH_MAX, &incoming->header.sourcePortIdentity); + + DBGV("Received GRANT_UNICAST_TRANSMISSION message for message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + + nodeTable = findUnicastGrants(&incoming->header.sourcePortIdentity, sourceAddress, grantTable, &ptpClock->grantIndex, nodeCount, TRUE); + + if(nodeTable == NULL) { + DBG("GRANT_UNICAST_TRANSMISSION: did not find node in master table: %s\n", portId); + return; + } + + if(msgIndex(messageType) < 0) { + DBG("Received unicast grant from %s for unsupported message type %d\n", inet_ntoa(tmpAddr), messageType); + return; + } + + myGrant = &nodeTable->grantData[msgIndex(messageType)]; + + if(!myGrant->requestable) { + DBG("received unicat grant for non-requestable message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + return; + } + + if(!myGrant->requested) { + DBG("received unicast grant for not requested message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + return; + } + + if(incomingGrant->durationField == 0) { + DBG("unicast transmission request for message %s interval %d duration %d s denied by %s(%s)\n", + getMessageTypeName(messageType), myGrant->logInterval, myGrant->duration, + portId, inet_ntoa(tmpAddr)); + + ptpClock->counters.unicastGrantsDenied++; + + + /* grant was denied so let's try a higher interval next time */ + myGrant->logInterval++; + + /* if we're above max, cycle through back to minimum */ + if(myGrant->logInterval > myGrant->logMaxInterval) { + myGrant->logInterval = myGrant->logMinInterval; + } + + /* this is so that we request again */ + myGrant->requested = FALSE; + + return; + } + + DBG("received unicast transmission grant for message %s, interval %d, duration %d s\n", + getMessageTypeName(messageType), incomingGrant->logInterMessagePeriod, + incomingGrant->durationField); + + ptpClock->counters.unicastGrantsGranted++; + + myGrant->granted = TRUE; + myGrant->logInterval = incomingGrant->logInterMessagePeriod; + myGrant->duration = incomingGrant->durationField; + myGrant->timeLeft = myGrant->duration; + myGrant->canceled = FALSE; + myGrant->cancelCount = 0; + +} + +/**\brief Handle incoming CANCEL_UNICAST_TRANSMISSION signaling message type*/ +static Boolean +handleSMCancelUnicastTransmission(MsgSignaling* incoming, MsgSignaling* outgoing, Integer32 sourceAddress, PtpClock* ptpClock) +{ + DBGV("Received CANCEL_UNICAST_TRANSMISSION message\n"); + + char portId[PATH_MAX]; + SMCancelUnicastTransmission* requestData = (SMCancelUnicastTransmission*)incoming->tlv->valueField; + SMAcknowledgeCancelUnicastTransmission* acknowledgeData = NULL; + + UnicastGrantData *myGrant; + Enumeration8 messageType = requestData->messageType; + UnicastGrantTable *nodeTable; +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + struct in_addr tmpAddr; + tmpAddr.s_addr = sourceAddress; +#endif /* RUNTIME_DEBUG */ + + initOutgoingMsgSignaling(&incoming->header.sourcePortIdentity, outgoing, ptpClock); + outgoing->header.flagField0 |= PTP_UNICAST; + outgoing->tlv->tlvType = TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION; + outgoing->tlv->lengthField = 2; + + XMALLOC(outgoing->tlv->valueField, sizeof(SMAcknowledgeCancelUnicastTransmission)); + acknowledgeData = (SMAcknowledgeCancelUnicastTransmission*)outgoing->tlv->valueField; + snprint_PortIdentity(portId, PATH_MAX, &incoming->header.sourcePortIdentity); + + DBGV("Received CANCEL_UNICAST_TRANSMISSION message for message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + + ptpClock->counters.unicastGrantsCancelReceived++; + + nodeTable = findUnicastGrants(&incoming->header.sourcePortIdentity, sourceAddress, ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, FALSE); + + if(nodeTable == NULL) { + DBG("CANCEL_UNICAST_TRANSMISSION: did not find node in slave table: %s\n", portId); + return FALSE; + } + + if(msgIndex(messageType) < 0) { + DBG("Received cancel unicast request from %s for unsupported message type %d\n", inet_ntoa(tmpAddr), messageType); + return FALSE; + } + + myGrant = &nodeTable->grantData[msgIndex(messageType)]; + + if(!myGrant->requestable) { + DBG("cancel grant attempt for non-requestable message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + return FALSE; + } + + if(!myGrant->requested) { + DBG("cancel grant attempt for not requested message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + return FALSE; + } + + if(!myGrant->granted) { + DBG("cancel grant attempt for not granted message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + return FALSE; + } + + myGrant->granted = FALSE; + myGrant->requested = FALSE; + + outgoing->header.sequenceId = myGrant->parent->grantData[SIGNALING_INDEXED].sentSeqId; + myGrant->parent->grantData[SIGNALING_INDEXED].sentSeqId++; + + myGrant->sentSeqId = 0; + myGrant->cancelCount = 0; + myGrant->timeLeft = 0; + myGrant->duration = 0; + + DBG("Accepted CANCEL_UNICAST_TRANSMISSION message for message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + + acknowledgeData->messageType = requestData->messageType; + acknowledgeData->reserved0 = 0; + acknowledgeData->reserved1 = 0; + + return TRUE; +} + +/**\brief Handle incoming ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION signaling message type*/ +static void +handleSMAcknowledgeCancelUnicastTransmission(MsgSignaling* incoming, Integer32 sourceAddress, PtpClock* ptpClock) +{ + + char portId[PATH_MAX]; + SMAcknowledgeCancelUnicastTransmission* requestData = (SMAcknowledgeCancelUnicastTransmission*)incoming->tlv->valueField; + + UnicastGrantData *myGrant; + Enumeration8 messageType = requestData->messageType; + UnicastGrantTable *nodeTable; +#if defined(RUNTIME_DEBUG) || defined (PTPD_DBGV) + struct in_addr tmpAddr; + tmpAddr.s_addr = sourceAddress; +#endif /* RUNTIME_DEBUG */ + + snprint_PortIdentity(portId, PATH_MAX, &incoming->header.sourcePortIdentity); + + DBGV("Received ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION message for message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + + nodeTable = findUnicastGrants(&incoming->header.sourcePortIdentity, sourceAddress, ptpClock->unicastGrants, &ptpClock->grantIndex, UNICAST_MAX_DESTINATIONS, FALSE); + + if(nodeTable == NULL) { + DBG("ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: did not find node in slave table: %s\n", portId); + } + + if(msgIndex(messageType) < 0) { + DBG("Received unicast acknowledge ancer from %s for unsupported message type %d\n", inet_ntoa(tmpAddr), messageType); + return; + } + + myGrant = &nodeTable->grantData[msgIndex(messageType)]; + + if(!myGrant->requestable) { + DBG("acknowledge cancel grant attempt for non-requestable message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + } + + if(!myGrant->canceled) { + DBG("acknowledge cancel grant received for not canceled message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + } + + myGrant->granted = FALSE; + myGrant->requested = FALSE; + myGrant->sentSeqId = 0; + myGrant->cancelCount = 0; + myGrant->timeLeft = 0; + myGrant->duration = 0; + myGrant->canceled = FALSE; + myGrant->cancelCount = 0; + + ptpClock->counters.unicastGrantsCancelAckReceived++; + + DBG("Accepted ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION message for message %s from %s(%s)\n", + getMessageTypeName(messageType), portId, inet_ntoa(tmpAddr)); + +} + +static Boolean +prepareSMRequestUnicastTransmission(MsgSignaling* outgoing, UnicastGrantData *grant, PtpClock* ptpClock) +{ + + DBGV("Preparing CANCEL_UNICAST_TRANSMISSION message\n"); + + /* Clause 16.1.4.1.3 */ + if(!grant->requestable) { + return FALSE; + } + + initOutgoingMsgSignaling(&grant->parent->portIdentity, outgoing, ptpClock); + + outgoing->header.flagField0 |= PTP_UNICAST; + outgoing->tlv->tlvType = TLV_REQUEST_UNICAST_TRANSMISSION; + outgoing->tlv->lengthField = 6; + + SMRequestUnicastTransmission* requestData = NULL; + + XMALLOC(outgoing->tlv->valueField, sizeof(SMRequestUnicastTransmission)); + requestData = (SMRequestUnicastTransmission*)outgoing->tlv->valueField; + + requestData->messageType = grant->messageType; + requestData->reserved0 = 0; + requestData->logInterMessagePeriod = grant->logInterval; + requestData->durationField = grant->duration; + + DBG(" prepared request unicast transmission request for message type 0x%0x\n", + grant->messageType); + + outgoing->header.sequenceId = grant->parent->grantData[SIGNALING_INDEXED].sentSeqId; + grant->parent->grantData[SIGNALING_INDEXED].sentSeqId++; + + return TRUE; + +} + +static Boolean +prepareSMCancelUnicastTransmission(MsgSignaling* outgoing, UnicastGrantData* grant, PtpClock* ptpClock) +{ + + DBGV("Preparing CANCEL_UNICAST_TRANSMISSION message\n"); + + initOutgoingMsgSignaling(&grant->parent->portIdentity, outgoing, ptpClock); + + outgoing->header.flagField0 |= PTP_UNICAST; + outgoing->tlv->tlvType = TLV_CANCEL_UNICAST_TRANSMISSION; + outgoing->tlv->lengthField = 2; + + SMCancelUnicastTransmission* cancelData = NULL; + + XMALLOC(outgoing->tlv->valueField, sizeof(SMCancelUnicastTransmission)); + cancelData = (SMCancelUnicastTransmission*)outgoing->tlv->valueField; + + grant->requested = FALSE; + grant->timeLeft = 0; + grant->expired = FALSE; + + if(!grant->requestable) { + DBG("Will not issue cancel unicast transmission for non-requestable message type 0x%0x\n", grant->messageType); + return FALSE; + } + + if(!grant->granted) { + DBG("Will not issue cancel unicast transmission for message type 0x%0x - not granted\n", grant->messageType); + return FALSE; + } + + grant->canceled = TRUE; + grant->cancelCount++; + + cancelData->messageType = grant->messageType; + + DBG(" prepared cancel unicast transmission request for message type %s\n", + getMessageTypeName(grant->messageType)); + + cancelData->reserved0 = 0; + cancelData->reserved1 = 0; + + outgoing->header.sequenceId = grant->parent->grantData[SIGNALING_INDEXED].sentSeqId; + grant->parent->grantData[SIGNALING_INDEXED].sentSeqId++; + + return TRUE; + +} + +/* prepare unicast grant table for use, and mark the right ones requestable */ +void +initUnicastGrantTable(UnicastGrantTable *grantTable, Enumeration8 delayMechanism, int nodeCount, UnicastDestination *destinations, + const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + int i,j; + + UnicastGrantData *grantData; + UnicastGrantTable *nodeTable; + + /* initialise the index table */ + for(i=0; i < UNICAST_MAX_DESTINATIONS; i++) { + ptpClock->grantIndex.data[i] = NULL; + ptpClock->syncDestIndex[i].transportAddress = 0; + } + + ptpClock->grantIndex.portMask = rtOpts->unicastPortMask; + + for(j=0; jtransportAddress = destinations[j].transportAddress; + nodeTable->domainNumber = destinations[j].domainNumber; + nodeTable->localPreference = destinations[j].localPreference; + if(nodeTable->domainNumber == 0) { + nodeTable->domainNumber = rtOpts->domainNumber; + } + /* for masters: all-ones initially */ + nodeTable->portIdentity.portNumber = 0xFFFF; + memset(&nodeTable->portIdentity.clockIdentity, 0xFF, CLOCK_IDENTITY_LENGTH); + } + + for(i=0; i< PTP_MAX_MESSAGE_INDEXED; i++) { + + grantData = &nodeTable->grantData[i]; + + memset(grantData, 0, sizeof(UnicastGrantData)); + + grantData->parent = nodeTable; + grantData->messageType = msgXedni(i); + + switch(grantData->messageType) { + + case PDELAY_RESP: + + grantData->logMinInterval = rtOpts->logMinPdelayReqInterval; + grantData->logMaxInterval = rtOpts->logMaxPdelayReqInterval; + grantData->logInterval = grantData->logMinInterval; + if(delayMechanism != P2P) break; + grantData->requestable = TRUE; + + break; + + case ANNOUNCE: + + grantData->logMinInterval = rtOpts->logAnnounceInterval; + grantData->logMaxInterval = rtOpts->logMaxAnnounceInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->requestable = TRUE; + break; + + case SYNC: + + grantData->logMinInterval = rtOpts->logSyncInterval; + grantData->logMaxInterval = rtOpts->logMaxSyncInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->requestable = TRUE; + break; + + case DELAY_RESP: + + if(delayMechanism != E2E) break; + grantData->logMinInterval = rtOpts->logMinDelayReqInterval; + grantData->logMaxInterval = rtOpts->logMaxDelayReqInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->requestable = TRUE; + break; + + default: + break; + } + + } + + } + + +} + + +/* update unicast grant table with configured intervals and expire them, + * so that messages are re-requested + */ +void +updateUnicastGrantTable(UnicastGrantTable *grantTable, int nodeCount, const RunTimeOpts *rtOpts) +{ + + int i,j; + + UnicastGrantData *grantData; + UnicastGrantTable *nodeTable; + + for(j=0; jgrantData[i]; + + if(!grantData->requestable) { + continue; + } + + switch(grantData->messageType) { + + case PDELAY_RESP: + + grantData->logMinInterval = rtOpts->logMinPdelayReqInterval; + grantData->logMaxInterval = rtOpts->logMaxPdelayReqInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->timeLeft = 0; + break; + + case ANNOUNCE: + + + grantData->logMinInterval = rtOpts->logAnnounceInterval; + grantData->logMaxInterval = rtOpts->logMaxAnnounceInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->timeLeft = 0; + break; + + case SYNC: + + grantData->logMinInterval = rtOpts->logSyncInterval; + grantData->logMaxInterval = rtOpts->logMaxSyncInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->timeLeft = 0; + break; + + case DELAY_RESP: + + grantData->logMinInterval = rtOpts->logMinDelayReqInterval; + grantData->logMaxInterval = rtOpts->logMaxDelayReqInterval; + grantData->logInterval = grantData->logMinInterval; + grantData->timeLeft = 0; + break; + + default: + break; + } + + + + } + + } + + +} + +static void +requestUnicastTransmission(UnicastGrantData *grant, UInteger32 duration, const RunTimeOpts* rtOpts, PtpClock* ptpClock) +{ + + if(duration == 0) { + DBG("Will not request unicast transmission for 0 duration\n"); + } + + grant->duration = duration; + + /* safeguard */ + if(duration < 5) { + grant->duration = 5; + } + + /* pack and send */ + if(prepareSMRequestUnicastTransmission(&ptpClock->outgoingSignalingTmp, grant, ptpClock)) { + if(grant->parent->domainNumber != 0) { + ptpClock->outgoingSignalingTmp.header.domainNumber = grant->parent->domainNumber; + } + issueSignaling(&ptpClock->outgoingSignalingTmp, grant->parent->transportAddress, rtOpts, ptpClock); + ptpClock->counters.unicastGrantsRequested++; + /* ready to be monitored */ + grant->requested = TRUE; + grant->timeLeft = 0; + grant->granted = FALSE; + grant->expired = FALSE; + } + + /* cleanup msgTmp signalingTLV */ + freeSignalingTLV(&ptpClock->msgTmp.signaling); + /* cleanup outgoing signalingTLV */ + freeSignalingTLV(&ptpClock->outgoingSignalingTmp); +} + +void +cancelUnicastTransmission(UnicastGrantData* grant, const const RunTimeOpts* rtOpts, PtpClock* ptpClock) +{ + +/* todo: dbg sending */ + + if(prepareSMCancelUnicastTransmission(&ptpClock->outgoingSignalingTmp, grant, ptpClock)) { + if(grant->parent->domainNumber != 0) { + ptpClock->outgoingSignalingTmp.header.domainNumber = grant->parent->domainNumber; + } + issueSignaling(&ptpClock->outgoingSignalingTmp, grant->parent->transportAddress, rtOpts, ptpClock); + ptpClock->counters.unicastGrantsCancelSent++; + } + + /* cleanup msgTmp signalingTLV */ + freeSignalingTLV(&ptpClock->msgTmp.signaling); + /* cleanup outgoing signalingTLV */ + freeSignalingTLV(&ptpClock->outgoingSignalingTmp); +} + +static void +issueSignaling(MsgSignaling *outgoing, Integer32 destination, const const RunTimeOpts *rtOpts, + PtpClock *ptpClock) +{ + + /* pack SignalingTLV */ + msgPackSignalingTLV( ptpClock->msgObuf, outgoing, ptpClock); + + /* set header messageLength, the outgoing->tlv->lengthField is now valid */ + outgoing->header.messageLength = SIGNALING_LENGTH + + TL_LENGTH + + outgoing->tlv->lengthField; + + msgPackSignaling( ptpClock->msgObuf, outgoing, ptpClock); + + if(!netSendGeneral(ptpClock->msgObuf, outgoing->header.messageLength, + &ptpClock->netPath, rtOpts, destination)) { + DBGV("Signaling message can't be sent -> FAULTY state \n"); + ptpClock->counters.messageSendErrors++; + toState(PTP_FAULTY, rtOpts, ptpClock); + } else { + DBG("Signaling msg sent \n"); + ptpClock->counters.signalingMessagesSent++; + } +} + +/* cancel all given or requested grants for a given clock node */ +static void +cancelNodeGrants(UnicastGrantTable *nodeTable, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + int i; + UnicastGrantData *grantData; + + for(i=0; i< PTP_MAX_MESSAGE_INDEXED; i++) { + grantData = &nodeTable->grantData[i]; + + if(!grantData->requestable) { + continue; + } + + if(grantData->granted) { + cancelUnicastTransmission(grantData, rtOpts, ptpClock); + /* sleep 250 to 500 us so that we don't flood the node */ + usleep(250+round(getRand()*250)); + } + + } +} + +/* cancel all given or requested unicast grants */ +void +cancelAllGrants(UnicastGrantTable *grantTable, int nodeCount, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + int i; + + for(i=0; iscoreConfig.autosar ) + { + DBG("autosar: dropping Signaling messages\n"); + ptpClock->counters.discardedMessages++; + return; + } +#endif + // ##### SCORE MODIFICATION END ##### + + /* loop over all supported TLVs as if they came in separate messages */ + while(msgUnpackSignaling(ptpClock->msgIbuf,&ptpClock->msgTmp.signaling, header, ptpClock, tlvOffset)) { + + if(ptpClock->msgTmp.signaling.tlv == NULL) { + + if(tlvFound==0) { + DBGV("handleSignaling: No TLVs in message\n"); + ptpClock->counters.messageFormatErrors++; + } else { + DBGV("handleSignaling: No more TLVs\n"); + } + return; + } + + tlvFound++; + + /* accept the message if directed either to us or to all-ones */ + if(!acceptPortIdentity(ptpClock->portDS.portIdentity, ptpClock->msgTmp.signaling.targetPortIdentity)) + { + DBG("handleSignaling: The signaling message was not accepted"); + ptpClock->counters.discardedMessages++; + goto end; + } + + /* Can we handle this? */ + + switch(ptpClock->msgTmp.signaling.tlv->tlvType) + { + case TLV_REQUEST_UNICAST_TRANSMISSION: + DBGV("handleSignaling: Request Unicast Transmission\n"); + if(!rtOpts->unicastNegotiation || rtOpts->ipMode!=IPMODE_UNICAST) { + DBGV("handleSignaling: Ignoring unicast negotiation message - not running unicast or negotiation not enabled\n"); + ptpClock->counters.discardedMessages++; + goto end; + } + if( (ptpClock->portDS.portState==PTP_LISTENING && !rtOpts->unicastNegotiationListening) || + ptpClock->portDS.portState==PTP_DISABLED || ptpClock->portDS.portState == PTP_INITIALIZING || + ptpClock->portDS.portState==PTP_FAULTY) { + DBG("Will not grant unicast transmission requests in %s state\n", + portState_getName(ptpClock->portDS.portState)); + ptpClock->counters.discardedMessages++; + goto end; + } + unpackSMRequestUnicastTransmission(ptpClock->msgIbuf + tlvOffset, &ptpClock->msgTmp.signaling, ptpClock); + handleSMRequestUnicastTransmission(&ptpClock->msgTmp.signaling, &ptpClock->outgoingSignalingTmp, sourceAddress, rtOpts, ptpClock); + /* send back the outgoing signaling message */ + if(ptpClock->outgoingSignalingTmp.tlv->tlvType == TLV_GRANT_UNICAST_TRANSMISSION) { + issueSignaling(&ptpClock->outgoingSignalingTmp, sourceAddress,rtOpts, ptpClock); + } + break; + case TLV_CANCEL_UNICAST_TRANSMISSION: + DBGV("handleSignaling: Cancel Unicast Transmission\n"); + if(!rtOpts->unicastNegotiation || rtOpts->ipMode!=IPMODE_UNICAST) { + DBGV("handleSignaling: Ignoring unicast negotiation message - not running unicast or negotiation not enabled\n"); + ptpClock->counters.discardedMessages++; + goto end; + } + if( (ptpClock->portDS.portState==PTP_LISTENING && !rtOpts->unicastNegotiationListening) || + ptpClock->portDS.portState==PTP_DISABLED || ptpClock->portDS.portState == PTP_INITIALIZING || + ptpClock->portDS.portState==PTP_FAULTY) { + DBG("Will not cancel unicast transmission requests in %s state\n", + portState_getName(ptpClock->portDS.portState)); + ptpClock->counters.discardedMessages++; + goto end; + } + unpackSMCancelUnicastTransmission(ptpClock->msgIbuf + tlvOffset, &ptpClock->msgTmp.signaling, ptpClock); + /* send back the cancel acknowledgment - if we have something to acknowledge*/ + if(handleSMCancelUnicastTransmission(&ptpClock->msgTmp.signaling, &ptpClock->outgoingSignalingTmp, sourceAddress, ptpClock)) { + issueSignaling(&ptpClock->outgoingSignalingTmp, sourceAddress,rtOpts, ptpClock); + ptpClock->counters.unicastGrantsCancelAckSent++; + } + break; + case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: + DBGV("handleSignaling: Acknowledge Cancel Unicast Transmission\n"); + if(!rtOpts->unicastNegotiation || rtOpts->ipMode!=IPMODE_UNICAST) { + DBGV("handleSignaling: Ignoring unicast negotiation message - not running unicast or negotiation not enabled\n"); + ptpClock->counters.discardedMessages++; + goto end; + } + if( (ptpClock->portDS.portState==PTP_LISTENING && !rtOpts->unicastNegotiationListening) || + ptpClock->portDS.portState==PTP_DISABLED || ptpClock->portDS.portState == PTP_INITIALIZING || + ptpClock->portDS.portState==PTP_FAULTY) { + DBG("Will not process acknowledge cancel unicast transmission in %s state\n", + portState_getName(ptpClock->portDS.portState)); + ptpClock->counters.discardedMessages++; + goto end; + } + unpackSMAcknowledgeCancelUnicastTransmission(ptpClock->msgIbuf + tlvOffset, &ptpClock->msgTmp.signaling, ptpClock); + handleSMAcknowledgeCancelUnicastTransmission(&ptpClock->msgTmp.signaling, sourceAddress, ptpClock); + break; + case TLV_GRANT_UNICAST_TRANSMISSION: + DBGV("handleSignaling: Grant Unicast Transmission\n"); + if(!rtOpts->unicastNegotiation || rtOpts->ipMode!=IPMODE_UNICAST) { + DBGV("handleSignaling: Ignoring unicast negotiation message - not running unicast or negotiation not enabled\n"); + ptpClock->counters.discardedMessages++; + goto end; + } + if( + ptpClock->portDS.portState==PTP_DISABLED || ptpClock->portDS.portState == PTP_INITIALIZING || + ptpClock->portDS.portState==PTP_FAULTY) { + DBG("Will not process acknowledge cancel unicast transmission in %s state\n", + portState_getName(ptpClock->portDS.portState)); + ptpClock->counters.discardedMessages++; + goto end; + } + unpackSMGrantUnicastTransmission(ptpClock->msgIbuf + tlvOffset, &ptpClock->msgTmp.signaling, ptpClock); + handleSMGrantUnicastTransmission(&ptpClock->msgTmp.signaling, sourceAddress, ptpClock->unicastGrants, ptpClock->unicastDestinationCount, ptpClock); + if(ptpClock->portDS.delayMechanism == P2P) { + handleSMGrantUnicastTransmission(&ptpClock->msgTmp.signaling, sourceAddress, &ptpClock->peerGrants, 1, ptpClock); + } + break; + + default: + + DBGV("handleSignaling: received unsupported TLV type %04x\n", + ptpClock->msgTmp.signaling.tlv->tlvType ); + ptpClock->counters.discardedMessages++; + goto end; + } + + end: + /* Movin' on up! */ + tlvOffset += TL_LENGTH + ptpClock->msgTmp.signaling.tlv->lengthField; + /* cleanup msgTmp signalingTLV */ + freeSignalingTLV(&ptpClock->msgTmp.signaling); + /* cleanup outgoing signalingTLV */ + freeSignalingTLV(&ptpClock->outgoingSignalingTmp); + } + + if(!tlvFound) { + DBGV("handleSignaling: No TLVs in message\n"); + ptpClock->counters.messageFormatErrors++; + } else { + ptpClock->counters.signalingMessagesReceived++; + DBGV("handleSignaling: No more TLVs\n"); + } + +} + +void +refreshUnicastGrants(UnicastGrantTable *grantTable, int nodeCount, const RunTimeOpts *rtOpts, PtpClock *ptpClock) +{ + + static int everyN = 0; + int i,j; + + UnicastGrantData *grantData = NULL; + UnicastGrantTable *nodeTable = NULL; + Boolean actionRequired; + int maxTime = 0; + + /* modulo N counter: used for requesting announce while other master is selected */ + everyN++; + everyN %= GRANT_KEEPALIVE_INTERVAL; + + /* only notSlave or slaveOnly - nothing inbetween */ + if(ptpClock->defaultDS.clockQuality.clockClass > 127 && ptpClock->defaultDS.clockQuality.clockClass < 255) { + return; + } + + + ptpClock->slaveCount = 0; + + for(j=0; jgrantData[i]; + if(grantData->granted && !grantData->expired) { + maxTime = grantData->timeLeft; + break; + } + } + + for(i=0; i < PTP_MAX_MESSAGE_INDEXED; i++) { + + grantData = &nodeTable->grantData[i]; + + if(!grantData->requestable) { + continue; + } + + actionRequired = FALSE; + + if(grantData->granted) { + /* re-request 5 seconds before expiry for continuous service. + * masters set this to +10 sec so will keep +5 sec extra + */ + if(grantData->timeLeft <= 5) { + DBG("grant for message %s expired\n", getMessageTypeName(grantData->messageType)); + grantData->expired = TRUE; + } else { + if(grantData->timeLeft > maxTime) { + maxTime = grantData->timeLeft; + } + grantData->timeLeft--; + } + + + } + + if(grantData->canceled && grantData->cancelCount >= GRANT_CANCEL_ACK_TIMEOUT) { + grantData->cancelCount = 0; + grantData->canceled = FALSE; + grantData->granted = FALSE; + grantData->requested = FALSE; + grantData->sentSeqId = 0; + grantData->timeLeft = 0; + grantData->duration = 0; + } + + if(grantData->expired || (ptpClock->defaultDS.slaveOnly && grantData->requested && !grantData->granted)) { + actionRequired = TRUE; + } + + if(nodeTable->isPeer && grantData->messageType==PDELAY_RESP && !grantData->granted) { + actionRequired = TRUE; + } + + if(!nodeTable->isPeer && ptpClock->defaultDS.slaveOnly) { + if(grantData->messageType == ANNOUNCE && !grantData->requested) { + actionRequired = TRUE; + } + } + + if((ptpClock->defaultDS.slaveOnly || nodeTable->isPeer) && (everyN == (GRANT_KEEPALIVE_INTERVAL -1))){ + if(grantData->receiving == 0 && grantData->granted ) { + /* if we mixed n consecutive messages (checked every m seconds), re-request */ + if( (everyN * UNICAST_GRANT_REFRESH_INTERVAL) > (GRANT_MAX_MISSED * grantData->logInterval)) { + DBG("foreign master: no %s being received - will request again\n", + getMessageTypeName(grantData->messageType)); + actionRequired = TRUE; + } + } + grantData->receiving = 0; + } + + /* if we're slave, we request; if we're master, we cancel */ + if(actionRequired) { + if (ptpClock->defaultDS.slaveOnly || nodeTable->isPeer) { + requestUnicastTransmission(grantData, rtOpts->unicastGrantDuration, rtOpts, ptpClock); + } else { + cancelUnicastTransmission(grantData, rtOpts, ptpClock); + } + } + + if(grantData->messageType == ANNOUNCE && ptpClock->portDS.portState == PTP_MASTER + && grantData->granted) { + ptpClock->slaveCount++; + } + + } + + nodeTable->timeLeft = maxTime; + /* Wild West version: Murdering Murphy! You done killed my paw! */ + /* Reggae version: Matic in dem way, chopper in dem hand, hey, some a dem have M16 'pon dem shoulder */ + /* Factual version: Make sure the node is re-usable: reset PortIdentity to all-ones again */ + if(nodeTable->timeLeft == 0) { + nodeTable->portIdentity.portNumber = 0xFFFF; + memset(&nodeTable->portIdentity.clockIdentity, 0xFF, CLOCK_IDENTITY_LENGTH); + DBG("Unicast node %d now free and reusable\n", j); + } + } + + if(nodeCount == 1 && nodeTable && nodeTable->isPeer) { + return; + } + /* we have some old requests to cancel, we changed the GM - keep the Announce coming though */ + if(ptpClock->previousGrants != NULL) { + cancelUnicastTransmission(&(ptpClock->previousGrants->grantData[SYNC_INDEXED]), rtOpts, ptpClock); + cancelUnicastTransmission(&(ptpClock->previousGrants->grantData[DELAY_RESP_INDEXED]), rtOpts, ptpClock); + cancelUnicastTransmission(&(ptpClock->previousGrants->grantData[PDELAY_RESP_INDEXED]), rtOpts, ptpClock); + /* do not reset the other master's clock ID! ...you little bollocks you */ + /* + ptpClock->previousGrants->portIdentity.portNumber = 0xFFFF; + memset(&ptpClock->previousGrants->portIdentity.clockIdentity, 0xFF, CLOCK_IDENTITY_LENGTH); + */ + ptpClock->previousGrants = NULL; + } + + if(ptpClock->defaultDS.slaveOnly && ptpClock->parentGrants != NULL && ptpClock->portDS.portState == PTP_SLAVE) { + + nodeTable = ptpClock->parentGrants; + + if (!nodeTable->grantData[SYNC_INDEXED].requested) { + grantData=&nodeTable->grantData[SYNC_INDEXED]; + requestUnicastTransmission(grantData, + rtOpts->unicastGrantDuration, rtOpts, ptpClock); + + } + + if (nodeTable->grantData[SYNC_INDEXED].granted) { + switch(ptpClock->portDS.delayMechanism) { + case E2E: + if(!nodeTable->grantData[DELAY_RESP_INDEXED].requested) { + grantData=&nodeTable->grantData[DELAY_RESP_INDEXED]; + requestUnicastTransmission(grantData, + rtOpts->unicastGrantDuration, rtOpts, ptpClock); + } + break; + case P2P: + if(!nodeTable->grantData[PDELAY_RESP_INDEXED].requested) { + grantData=&nodeTable->grantData[PDELAY_RESP_INDEXED]; + requestUnicastTransmission(grantData, + rtOpts->unicastGrantDuration, rtOpts, ptpClock); + } + break; + default: + break; + } + + } + + } + +} diff --git a/src/ptpd/src/templates.conf b/src/ptpd/src/templates.conf new file mode 100644 index 0000000..91253f4 --- /dev/null +++ b/src/ptpd/src/templates.conf @@ -0,0 +1,22 @@ +; templates.conf - PTPd user template file. + +; this file can be used to add PTPd config templates loaded on startup +; this file is always loaded if any templates are specified using: +; global:config_templates + +; PTPd also provides built-in templates. To see what is provided, +; run ptpd with -T or --show-templates (or as part of full help, -H) + +; for more details, see man ptpd2(8) and ptpd2.conf(5) + +; As multiple templates are loaded, multiple instances of the same +; template are merged - one template file can extend templates +; in previously loaded files and can extend the built-in templates. +; This file can be used for this purpose. + +; example entry: + +; [template-name] +; section:setting=1"value" +; section:setting2="value" + diff --git a/src/ptpd/src/timingdomain.c b/src/ptpd/src/timingdomain.c new file mode 100644 index 0000000..706af9a --- /dev/null +++ b/src/ptpd/src/timingdomain.c @@ -0,0 +1,969 @@ +#include "ptpd.h" +#include "dep/ntpengine/ntpdcontrol.h" + +#ifdef LOCAL_PREFIX +#undef LOCAL_PREFIX +#endif + +#define LOCAL_PREFIX "TimingService" + +static int cmpTimingService(TimingService *a, TimingService *b, Boolean useableOnly); +static const char* reasonToString(int reason); +static void prepareLeapFlags(RunTimeOpts *rtOpts, PtpClock *ptpClock); + +static int ptpServiceInit (TimingService* service); +static int ptpServiceShutdown (TimingService* service); +static int ptpServiceAcquire (TimingService* service); +static int ptpServiceRelease (TimingService* service, int reason); +static int ptpServiceUpdate (TimingService* service); +static int ptpServiceClockUpdate (TimingService* service); + +static int ntpServiceInit (TimingService* service); +static int ntpServiceShutdown (TimingService* service); +static int ntpServiceAcquire (TimingService* service); +static int ntpServiceRelease (TimingService* service, int reason); +static int ntpServiceUpdate (TimingService* service); +static int ntpServiceClockUpdate (TimingService* service); + + +static int timingDomainInit(TimingDomain *domain); +static int timingDomainShutdown(TimingDomain *domain); +static int timingDomainUpdate(TimingDomain *domain); + +int +timingDomainSetup(TimingDomain *domain) +{ + domain->init = timingDomainInit; + domain->shutdown = timingDomainShutdown; + domain->update = timingDomainUpdate; + domain->current = NULL; + return 1; +} + +int +timingServiceSetup(TimingService *service) +{ + + if(service == NULL) { + return 0; + } + + switch (service->dataSet.type) { + + case TIMINGSERVICE_NTP: + service->init = ntpServiceInit; + service->shutdown = ntpServiceShutdown; + service->acquire = ntpServiceAcquire; + service->release = ntpServiceRelease; + service->update = ntpServiceUpdate; + service->clockUpdate = ntpServiceClockUpdate; + break; + case TIMINGSERVICE_PTP: + service->init = ptpServiceInit; + service->shutdown = ptpServiceShutdown; + service->acquire = ptpServiceAcquire; + service->release = ptpServiceRelease; + service->update = ptpServiceUpdate; + service->clockUpdate = ptpServiceClockUpdate; + break; + default: + break; + } + + return 1; + +} + +static +const char* +reasonToString(int reason) { + switch(reason) { + + case REASON_IDLE: + return("idle"); + case REASON_ELECTION: + return("election"); + case REASON_CTRL_NOT_BEST: + return("in control but not elected"); + case REASON_ELIGIBLE: + return("no longer eligible"); + default: + return ""; + } +} + + +/* a'la BMCA. Eventually quality estimation will get here and the fun begins */ +static int +cmpTimingService(TimingService *a, TimingService *b, Boolean useableOnly) +{ + + /* if called with false, only dataset is examined */ + if(useableOnly) { + /* operational is always better */ + /* well.. for now. */ + CMP2H((a->flags & TIMINGSERVICE_OPERATIONAL), + (b->flags & TIMINGSERVICE_OPERATIONAL)) + /* only evaluate if the service wants to control the clock. */ + /* this is meant to select the best service that can be used right now */ + CMP2H((a->flags & TIMINGSERVICE_AVAILABLE), + (b->flags & TIMINGSERVICE_AVAILABLE)) + /* should not happen really, when idle is triggered, available goes */ +// CMP2L((a->flags & TIMINGSERVICE_IDLE), +// (b->flags & TIMINGSERVICE_IDLE)) + } + /* lower p1 = better */ + CMP2L(a->dataSet.priority1, b->dataSet.priority1); + /* lower type = better */ + CMP2L((uint8_t)a->dataSet.type, (uint8_t)b->dataSet.type); + /* lower p2 = better */ + CMP2L(a->dataSet.priority2, b->dataSet.priority2); + + /* tiebreaker needed eventually: some uuid? age? */ + /* CMP2L(a->dataSet.TBD, b->dataSet.TBD); */ + + /* + * Son, you don't have bad luck. + * The reason that bad things happen to you, + * is because you're a dumbass + */ + return 1; +} + +/* version suitable for quicksort */ +/* +static int +cmpTimingServiceQS (void *pA, void *pB) +{ + + TimingService *a = (TimingService*)pA; + TimingService *b = (TimingService*)pB; + + return cmpTimingService(a, b, TRUE); + +} +*/ + +static int +ptpServiceInit (TimingService* service) +{ + RunTimeOpts *rtOpts = (RunTimeOpts*)service->config; + PtpClock *ptpClock = (PtpClock*)service->controller; + + memset(&rtOpts->leapInfo, 0, sizeof(LeapSecondInfo)); + if(strcmp(rtOpts->leapFile,"")) { + parseLeapFile(rtOpts->leapFile, &rtOpts->leapInfo); + } + + /* read current UTC offset from leap file or from kernel if not configured */ + if(ptpClock->timePropertiesDS.ptpTimescale && + rtOpts->timeProperties.currentUtcOffset == 0) { + prepareLeapFlags(rtOpts, ptpClock); + } + + INFO_LOCAL_ID(service, "PTP service init\n"); + + return 1; +} + +static int +ptpServiceShutdown (TimingService* service) +{ + PtpClock *ptpClock = (PtpClock*)service->controller; + INFO_LOCAL_ID(service,"PTP service shutdown\n"); + ptpdShutdown(ptpClock); + return 1; +} + +static int +ptpServiceAcquire (TimingService* service) +{ +// RunTimeOpts *rtOpts = (RunTimeOpts*)service->config; + PtpClock *ptpClock = (PtpClock*)service->controller; + ptpClock->clockControl.granted = TRUE; + INFO_LOCAL_ID(service,"acquired clock control\n"); + FLAGS_SET(service->flags, TIMINGSERVICE_IN_CONTROL); + return 1; +} + +static int +ptpServiceRelease (TimingService* service, int reason) +{ +// RunTimeOpts *rtOpts = (RunTimeOpts*)service->config; + PtpClock *ptpClock = (PtpClock*)service->controller; + ptpClock->clockControl.granted = FALSE; + if(!service->released) INFO_LOCAL_ID(service,"released clock control, reason: %s\n", + reasonToString(reason)); + FLAGS_UNSET(service->flags, TIMINGSERVICE_IN_CONTROL); + return 1; +} + +/* + * configure the UTC offset and leap flags according to + * information from kernel or leap file. Note: no updates to ptpClock. + * only clockStatus is being picked up in protocol.c + */ +static void +prepareLeapFlags(RunTimeOpts *rtOpts, PtpClock *ptpClock) { + + TimeInternal now; + Boolean leapInsert = FALSE, leapDelete = FALSE; +#ifdef HAVE_SYS_TIMEX_H + int flags; + + /* first get the offset from kernel if we can */ +#if defined(MOD_TAI) && NTP_API == 4 + int utcOffset; + + if(getKernelUtcOffset(&utcOffset) && utcOffset != 0) { + ptpClock->clockStatus.utcOffset = utcOffset; + } +#endif /* MOD_TAI */ + + flags= getTimexFlags(); + + leapInsert = ((flags & STA_INS) == STA_INS); + leapDelete = ((flags & STA_DEL) == STA_DEL); + +#endif /* HAVE_SYS_TIMEX_H */ + + getTime(&now); + + + ptpClock->clockStatus.override = FALSE; + + /* then we try the offset from leap file if valid - takes priority over kernel */ + if(rtOpts->leapInfo.offsetValid) { + ptpClock->clockStatus.utcOffset = + rtOpts->leapInfo.currentOffset; + ptpClock->clockStatus.override = TRUE; + } + + /* if we have valid leap second info from leap file, we use it */ + if(rtOpts->leapInfo.valid) { + + ptpClock->clockStatus.leapInsert = FALSE; + ptpClock->clockStatus.leapDelete = FALSE; + + if( now.seconds >= rtOpts->leapInfo.startTime && + now.seconds < rtOpts->leapInfo.endTime) { + DBG("Leap second pending - leap file\n"); + if(rtOpts->leapInfo.leapType == 1) { + ptpClock->clockStatus.leapInsert = TRUE; + } + if(rtOpts->leapInfo.leapType == -1) { + ptpClock->clockStatus.leapDelete = TRUE; + } + + ptpClock->clockStatus.override = TRUE; + + } + if(now.seconds >= rtOpts->leapInfo.endTime) { + ptpClock->clockStatus.utcOffset = + rtOpts->leapInfo.nextOffset; + ptpClock->clockStatus.override = TRUE; + if(strcmp(rtOpts->leapFile,"")) { + memset(&rtOpts->leapInfo, 0, sizeof(LeapSecondInfo)); + parseLeapFile(rtOpts->leapFile, &rtOpts->leapInfo); + + if(rtOpts->leapInfo.offsetValid) { + ptpClock->clockStatus.utcOffset = + rtOpts->leapInfo.currentOffset; + + } + + } + + } + /* otherwise we try using the kernel info, but not when we're slave */ + } else if(ptpClock->portDS.portState != PTP_SLAVE) { + ptpClock->clockStatus.leapInsert = leapInsert; + ptpClock->clockStatus.leapDelete = leapDelete; + } + +} + +static int +ptpServiceUpdate (TimingService* service) +{ + RunTimeOpts *rtOpts = (RunTimeOpts*)service->config; + PtpClock *ptpClock = (PtpClock*)service->controller; + + ptpClock->counters.messageSendRate = ptpClock->netPath.sentPackets / service->updateInterval; + ptpClock->counters.messageReceiveRate = ptpClock->netPath.receivedPackets / service->updateInterval; + + ptpClock->netPath.sentPackets = 0; + ptpClock->netPath.receivedPackets = 0; + + if(service->reloadRequested && strcmp(rtOpts->leapFile,"")) { + memset(&rtOpts->leapInfo, 0, sizeof(LeapSecondInfo)); + parseLeapFile(rtOpts->leapFile, &rtOpts->leapInfo); + service->reloadRequested = FALSE; + } + + /* read current UTC offset from leap file or from kernel if not configured */ + if(ptpClock->timePropertiesDS.ptpTimescale && ( ptpClock->portDS.portState == PTP_SLAVE || + (ptpClock->portDS.portState == PTP_MASTER && rtOpts->timeProperties.currentUtcOffset == 0))) { + prepareLeapFlags(rtOpts, ptpClock); + } + + /* temporary: this is only to maintain PTPd's current config options */ + /* if NTP failover disabled, pretend PTP always owns the clock */ + if(!rtOpts->ntpOptions.enableFailover) { + FLAGS_SET(service->flags, TIMINGSERVICE_OPERATIONAL); + if(ptpClock->clockControl.available) { + ptpClock->clockControl.granted = TRUE; + } + service->activity = TRUE; + FLAGS_SET(service->flags, TIMINGSERVICE_AVAILABLE); + return 1; + } + + /* keep the activity heartbeat in check */ + if(ptpClock->clockControl.activity) { + service->activity = TRUE; + ptpClock->clockControl.activity = FALSE; + DBGV("TimingService %s activity seen\n", service->id); + } else { + service->activity = FALSE; + } + + /* initializing or faulty: not operational */ + if((ptpClock->portDS.portState == PTP_INITIALIZING) || + (ptpClock->portDS.portState == PTP_FAULTY)) { + FLAGS_UNSET(service->flags, TIMINGSERVICE_OPERATIONAL); + } else { + FLAGS_SET(service->flags, TIMINGSERVICE_OPERATIONAL); + DBGV("TimingService %s is operational\n", service->id); + } + + /* not slave: release control or start hold timer */ + if(ptpClock->portDS.portState != PTP_SLAVE) { + FLAGS_UNSET(service->flags, TIMINGSERVICE_IDLE); + if(service->flags & TIMINGSERVICE_HOLD) { + if((service->holdTimeLeft)<=0) { + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + FLAGS_UNSET(service->flags, TIMINGSERVICE_HOLD); + + ptpClock->clockControl.available = FALSE; + if(service->holdTime) { + INFO_LOCAL_ID(service,"hold time expired\n"); + } + } + } else if(ptpClock->clockControl.available) { + FLAGS_SET(service->flags, TIMINGSERVICE_HOLD); + /* if we're already in hold time, don't restart it */ + if(service->holdTimeLeft <=0) { + service->holdTimeLeft = service->holdTime; + } + if(service->holdTimeLeft>0) { + DBG_LOCAL_ID(service,"hold started - %d seconds\n",service->holdTimeLeft); + } + } + } else { + /* slave: ready to acquire clock control, hold time over */ + if(ptpClock->clockControl.available) { + if(!(service->flags & TIMINGSERVICE_AVAILABLE)) { + + INFO_LOCAL_ID(service,"now available\n"); + FLAGS_SET(service->flags, TIMINGSERVICE_AVAILABLE); + + } + + if(service->flags & TIMINGSERVICE_HOLD) { + DBG_LOCAL_ID(service, "hold cancelled\n"); + FLAGS_UNSET(service->flags, TIMINGSERVICE_HOLD); + service->holdTimeLeft = 0; + } + + DBGV("TimingService %s available for clock control\n", service->id); + } else { + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + } + } + + /* if we're idle, release clock control */ + if((service->flags & TIMINGSERVICE_IDLE) && (service->holdTimeLeft <= 0)) { + ptpClock->clockControl.available = FALSE; + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + } + + return 1; +} + +static int +ptpServiceClockUpdate (TimingService* service) +{ + RunTimeOpts *rtOpts = (RunTimeOpts*)service->config; + PtpClock *ptpClock = (PtpClock*)service->controller; + + TimeInternal newTime, oldTime; + +#ifdef HAVE_SYS_TIMEX_H + int flags = getTimexFlags(); + + Boolean leapInsert = flags & STA_INS; + Boolean leapDelete = flags & STA_DEL; + Boolean inSync = !(flags & STA_UNSYNC); +#endif /* HAVE_SYS_TIMEX_H */ + + ClockStatusInfo *clockStatus = &ptpClock->clockStatus; + + + if(!clockStatus->update) { + return 0; + } + + DBG_LOCAL_ID(service, "clock status update\n"); + +#if defined(MOD_TAI) && NTP_API == 4 + setKernelUtcOffset(clockStatus->utcOffset); + + DBG_LOCAL_ID(service,"Set kernel UTC offset to %d\n", + clockStatus->utcOffset); +#endif + +#ifdef HAVE_SYS_TIMEX_H + + if(clockStatus->inSync & !inSync) { + clockStatus->inSync = FALSE; + unsetTimexFlags(STA_UNSYNC, TRUE); + } + + informClockSource(ptpClock); + + if(rtOpts->leapSecondHandling == LEAP_ACCEPT) { + /* withdraw kernel flags if needed, unless this is during the event */ + if(!ptpClock->leapSecondInProgress) { + if(leapInsert && !clockStatus->leapInsert) { + DBG_LOCAL_ID(service,"STA_INS in kernel but not in PTP: withdrawing\n"); + unsetTimexFlags(STA_INS, TRUE); + } + + if(leapDelete && !clockStatus->leapDelete) { + DBG_LOCAL_ID(service,"STA_DEL in kernel but not in PTP: withdrawing\n"); + unsetTimexFlags(STA_DEL, TRUE); + } + } + + if(!leapInsert && clockStatus->leapInsert) { + WARNING("Leap second pending! Setting clock to insert one second at midnight\n"); + setTimexFlags(STA_INS, FALSE); + } + + if(!leapDelete && clockStatus->leapDelete) { + WARNING("Leap second pending! Setting clock to delete one second at midnight\n"); + setTimexFlags(STA_DEL, FALSE); + } + } else { + if(leapInsert || leapDelete) { + DBG("leap second handling is not set to accept - withdrawing kernel leap flags!\n"); + unsetTimexFlags(STA_INS, TRUE); + unsetTimexFlags(STA_DEL, TRUE); + } + } +#else + if(clockStatus->leapInsert || clockStatus->leapDelete) { + if(rtOpts->leapSecondHandling != LEAP_SMEAR) { + WARNING("Leap second pending! No kernel leap second " + "API support - expect a clock offset at " + "midnight!\n"); + } + } +#endif /* HAVE_SYS_TIMEX_H */ + + getTime(&oldTime); + subTime(&newTime, &oldTime, &ptpClock->currentDS.offsetFromMaster); + + /* Major time change */ + if(clockStatus->majorChange){ + /* re-parse leap seconds file */ + if(strcmp(rtOpts->leapFile,"")) { + memset(&rtOpts->leapInfo, 0, sizeof(LeapSecondInfo)); + parseLeapFile(rtOpts->leapFile, &rtOpts->leapInfo); + } +#ifdef HAVE_LINUX_RTC_H + if(rtOpts->setRtc) { + NOTICE_LOCAL_ID(service, "Major time change - syncing the RTC\n"); + setRtc(&newTime); + clockStatus->majorChange = FALSE; + } +#endif /* HAVE_LINUX_RTC_H */ + /* need to inform utmp / wtmp */ + if(oldTime.seconds != newTime.seconds) { + updateXtmp(oldTime, newTime); + } + } + + ptpClock->clockStatus.update = FALSE; + return 1; +} + +static int +ntpServiceInit (TimingService* service) +{ + NTPoptions *config = (NTPoptions*) service->config; + NTPcontrol *controller = (NTPcontrol*) service->controller; + + INFO_LOCAL_ID(service,"NTP service init\n"); + + if(!config->enableEngine) { + INFO_LOCAL_ID(service,"NTP service not enabled\n"); + FLAGS_UNSET(service->flags, TIMINGSERVICE_OPERATIONAL); + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + return 1; + } + + if(ntpInit(config, controller)) { + FLAGS_SET(service->flags, TIMINGSERVICE_OPERATIONAL); + INFO_LOCAL_ID(service,"NTP service started\n"); + return 1; + } else { + FLAGS_UNSET(service->flags, TIMINGSERVICE_OPERATIONAL); + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + INFO_LOCAL_ID(service,"Could not start NTP service. Will keep retrying.\n"); + return 0; + } + +} + +static int +ntpServiceShutdown (TimingService* service) +{ + NTPoptions *config = (NTPoptions*) service->config; + NTPcontrol *controller = (NTPcontrol*) service->controller; + + INFO_LOCAL_ID(service,"NTP service shutting down\n"); + + if(controller->flagsCaptured) { + INFO_LOCAL_ID(service,"Restoring original NTP state\n"); + ntpShutdown(config, controller); + } + + FLAGS_UNSET(service->flags, TIMINGSERVICE_OPERATIONAL); + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + return 1; +} + +static int +ntpServiceAcquire (TimingService* service) +{ + NTPoptions *config = (NTPoptions*) service->config; + NTPcontrol *controller = (NTPcontrol*) service->controller; + + if(!config->enableControl) { + if (!controller->requestFailed) { + WARNING_LOCAL_ID(service, "control disabled, cannot acquire clock control\n"); + } + controller->requestFailed = TRUE; + return 1; + } + + switch(ntpdSetFlags(config, controller, SYS_FLAG_KERNEL | SYS_FLAG_NTP)) { + case INFO_OKAY: + FLAGS_SET(service->flags, TIMINGSERVICE_IN_CONTROL); + controller->requestFailed = FALSE; + INFO_LOCAL_ID(service, "acquired clock control\n"); + return 1; + default: + if (!controller->requestFailed) { + WARNING_LOCAL_ID(service,"failed to acquire clock control - clock may drift!\n"); + } + controller->requestFailed = TRUE; + return 0; + } + +} + +static int +ntpServiceRelease (TimingService* service, int reason) +{ + NTPoptions *config = (NTPoptions*) service->config; + NTPcontrol *controller = (NTPcontrol*) service->controller; + + int res = 0; + + if(!config->enableControl) { + if (!controller->requestFailed) { + WARNING_LOCAL_ID(service, "control disabled, cannot release clock control\n"); + } + controller->requestFailed = TRUE; + return 1; + } + + res = ntpdClearFlags(config, controller, SYS_FLAG_KERNEL | SYS_FLAG_NTP); + + switch(res) { + case INFO_OKAY: + controller->requestFailed = FALSE; + if(!service->released) INFO_LOCAL_ID(service, "released clock control, reason: %s\n", + reasonToString(reason)); + FLAGS_UNSET(service->flags, TIMINGSERVICE_IN_CONTROL); + return 1; + default: + if(!controller->requestFailed) { + WARNING_LOCAL_ID(service,"failed to release clock control, reason: %s - clock may be unstable!\n", + reasonToString(reason)); + } + controller->requestFailed = TRUE; + return 0; + } + +} + +static int +ntpServiceUpdate (TimingService* service) +{ + + int res; + + NTPoptions *config = (NTPoptions*) service->config; + NTPcontrol *controller = (NTPcontrol*) service->controller; + + if(!config->enableEngine) { + return 0; + } + + res = ntpdInControl(config, controller); + + if (res != INFO_YES && res != INFO_NO) { + if(!controller->checkFailed) { + WARNING_LOCAL_ID(service,"Could not verify NTP status - will keep checking\n"); + } + controller->checkFailed = TRUE; + FLAGS_UNSET(service->flags, TIMINGSERVICE_OPERATIONAL); + FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + return 0; + } + + FLAGS_SET(service->flags, TIMINGSERVICE_OPERATIONAL); + + if(service->flags & TIMINGSERVICE_AVAILABLE) { + service->activity = TRUE; + } else { + controller->checkFailed = FALSE; + INFO_LOCAL_ID(service,"now available\n"); + FLAGS_SET(service->flags, TIMINGSERVICE_AVAILABLE); + } + + /* + * notify the service that we are in control, + * so that the watchdog can react if we are and it wasn't granted + */ + if (res == INFO_YES) { + FLAGS_SET(service->flags, TIMINGSERVICE_IN_CONTROL); + } + + controller->checkFailed = FALSE; + + return 1; +} + +static int +ntpServiceClockUpdate (TimingService* service) +{ + return 1; +} + +static int +timingDomainInit(TimingDomain *domain) +{ + int i = 0; + + TimingService *service; + + DBG("Timing domain init\n"); + + for(i=0; i < domain->serviceCount; i++) { + service = domain->services[i]; + timingServiceSetup(service); + service->init(service); + service->parent = domain; + } + + domain->current = NULL; + domain->best = NULL; + domain->preferred = NULL; + + return 1; + +} + +static int +timingDomainShutdown(TimingDomain *domain) +{ + int i = 0; + + TimingService *service; + + INFO_LOCAL("Timing domain shutting down\n"); + + for(i=domain->serviceCount - 1; i >= 0; i--) { + service = domain->services[i]; + if(service != NULL) { + service->shutdown(service); + } + } + + INFO_LOCAL("Timing domain shutdown complete\n"); + + return 1; +} + +static int +timingDomainUpdate(TimingDomain *domain) +{ + + int i = 0; + int cmp = 0; + + TimingService *best = NULL; + TimingService *preferred = NULL; + TimingService *service = NULL; + + + /* update the election delay timer */ + if(domain->electionLeft > 0) { + if(domain->electionLeft == domain->electionDelay) { + NOTIFY_LOCAL("election hold timer started: %d seconds\n", domain->electionDelay); + } + domain->electionLeft -= domain->updateInterval; + DBG_LOCAL("electionLeft %d\n", domain->electionLeft); + } + + DBGV("Timing domain update\n"); + + if(domain->serviceCount < 1) { + DBG("No TimingServices in TimingDomain: nothing to do\n"); + return 0; + } + + /* first pass: check if alive, update idle times, release where needed */ + for(i=0; i < domain->serviceCount; i++) { + + service = domain->services[i]; + + service->lastUpdate += domain->updateInterval; + + /* decrement the hold time counter */ + if(service->holdTimeLeft > 0) { + service->holdTimeLeft -= domain->updateInterval; + DBG_LOCAL_ID(service, "hold time left %d\n", service->holdTimeLeft); + } else { + service->holdTimeLeft = 0; + } + + /* each TimingService can have a different update interval: skip when not due */ + if(service->lastUpdate >= service->updateInterval) { + service->updateDue = TRUE; + service->lastUpdate = 0; + } else { + continue; + } + + DBG("TimingService %s due for update\n", service->id); + service->update(service); + + + DBGV("Service %s flags %03x\n",service->id, service->flags); + + /* update idle times for operational services */ + if(service->flags & TIMINGSERVICE_OPERATIONAL) { + + if(!service->activity) { + service->idleTime += domain->updateInterval; + } else { + if(service->flags & TIMINGSERVICE_IDLE) + INFO_LOCAL_ID(service,"no longer idle\n"); + FLAGS_UNSET(service->flags, TIMINGSERVICE_IDLE); + service->idleTime = 0; + if((service->holdTimeLeft>0) && !(service->flags & TIMINGSERVICE_HOLD)) { + service->holdTimeLeft = 0; + } + } + + service->activity = FALSE; + + if( (service->flags & TIMINGSERVICE_AVAILABLE) && + !(service->flags & TIMINGSERVICE_HOLD) && + (service->idleTime > service->minIdleTime) && + (service->idleTime > service->timeout)) { + + service->idleTime = 0; + if(!(service->flags & TIMINGSERVICE_IDLE)) { + INFO_LOCAL_ID(service, "has gone idle\n"); + if(service == domain->current) { + service->holdTimeLeft = service->holdTime; + } + } + + if(service->holdTimeLeft <= 0) { + // FLAGS_UNSET(service->flags, TIMINGSERVICE_AVAILABLE); + } + + FLAGS_SET(service->flags, TIMINGSERVICE_IDLE); + if((service == domain->current) && + (service->holdTimeLeft <= 0)) { + INFO_LOCAL_ID(service,"idle time hold start\n"); + service->release(service, REASON_IDLE); + service->released = TRUE; + domain->electionLeft = domain->electionDelay; + domain->current = NULL; + + } + + } + } + + /* inactive or inoperational and in control: release */ + if((!(service->flags & TIMINGSERVICE_AVAILABLE) || + !(service->flags & TIMINGSERVICE_OPERATIONAL)) && + (service->flags & TIMINGSERVICE_IN_CONTROL) ) { + if(service->holdTimeLeft <= 0) { + service->release(service, REASON_ELIGIBLE); + service->released = TRUE; + if(service == domain->current) { + domain->electionLeft = domain->electionDelay; + domain->current = NULL; + } + } + } + + } + + /* second pass: elect the best service, establish preferred */ + best = domain->services[0]; + preferred = domain->services[0]; + + for(i=0; i < domain->serviceCount; i++) { + service = domain->services[i]; + cmp = cmpTimingService(service, best, TRUE); + DBG("TimingService %s vs. best %s: cmp %d\n", service->id, best->id, cmp); + if(cmp >= 0) { + best = service; + } + + if(cmpTimingService(service, preferred, FALSE) > 0) { + preferred = service; + } + + } + + /* best service, does not mean best selected */ + domain->best = best; + + /* best from DS perspective only */ + domain->preferred = preferred; + + /* best has changed - release previous, but let it know about the new best */ + if((best != domain->current) && (domain->electionLeft == 0)) { + /* here used as temp variable */ + service = domain->current; + + /* release previous if we had one, kick off election hold */ + if (service != NULL && (service->holdTimeLeft <= 0)) { + service->release(service, REASON_ELECTION); + service->released = TRUE; + domain->electionLeft = domain->electionDelay; + domain->current = NULL; + /* this way the election is 1 check interval minimum */ + return 1; + } + + domain->current = best; + + if(FLAGS_ARESET(best->flags, TIMINGSERVICE_OPERATIONAL | TIMINGSERVICE_AVAILABLE)) { + NOTIFY_LOCAL_ID(best,"elected best TimingService\n"); + } else { + domain->current = NULL; + domain->best = NULL; + } + } + + /* third pass: release services in control which aren't best (can happen) */ + for(i=0; i < domain->serviceCount; i++) { + service = domain->services[i]; + if((service != domain->current) && (service->flags & TIMINGSERVICE_IN_CONTROL)) { + if (service->updateDue) { + if(service->holdTimeLeft <= 0) { + service->release(service, REASON_CTRL_NOT_BEST); + service->released = FALSE; + } + } + } + } + + /* fourth pass: do the sums */ + + domain->availableCount = 0; + domain->operationalCount = 0; + domain->idleCount = 0; + domain->controlCount = 0; + + for(i=0; i < domain->serviceCount; i++) { + + service = domain->services[i]; + + if(service->flags & TIMINGSERVICE_OPERATIONAL) + domain->operationalCount++; + + if(service->flags & TIMINGSERVICE_AVAILABLE) + domain->availableCount++; + + if(service->flags & TIMINGSERVICE_IDLE) + domain->idleCount++; + + if(service->flags & TIMINGSERVICE_IN_CONTROL) + domain->controlCount++; + + } + + /* special case, aye */ + if((domain->serviceCount == 1) && (domain->current == NULL)) { + domain->best = NULL; + } + + /* clear updateDue */ + for(i=0; i < domain->serviceCount; i++) { + domain->services[i]->updateDue = FALSE; + } + + if(best == NULL) { + if (!domain->noneAvailable) + WARNING_LOCAL("No TimingService available\n"); + domain->noneAvailable = TRUE; + domain->current = NULL; + return 0; + } + + if(!(best->flags & TIMINGSERVICE_OPERATIONAL)) { + if (!domain->noneAvailable) + WARNING_LOCAL("No operational TimingService available\n"); + domain->noneAvailable = TRUE; + domain->current = NULL; + return 0; + } + + if(!(best->flags & TIMINGSERVICE_AVAILABLE)) { + if (!domain->noneAvailable) + WARNING_LOCAL("No TimingService available for clock sync\n"); + domain->noneAvailable = TRUE; + domain->current = NULL; + return 0; + } + + domain->noneAvailable = FALSE; + + if(!(best->flags & TIMINGSERVICE_IN_CONTROL) && domain->electionLeft == 0) { + best->released = FALSE; + best->acquire(best); + } + + /* update clock source info */ + if(best->flags & TIMINGSERVICE_IN_CONTROL) { + best->clockUpdate(best); + } + + return 1; +} + diff --git a/src/ptpd/src/timingdomain.h b/src/ptpd/src/timingdomain.h new file mode 100644 index 0000000..046872b --- /dev/null +++ b/src/ptpd/src/timingdomain.h @@ -0,0 +1,131 @@ +#ifndef TIMINGDOMAIN_H_ +#define TIMINGDOMAIN_H_ + +/* simple compare 2, lower wins */ +#define CMP2L(a,b) \ + if (a < b ) return 1;\ + if (a > b ) return -1; + +/* simple compare 2, higher wins */ +#define CMP2H(a,b) \ + if (a > b ) return 1;\ + if (a < b ) return -1; + +/* respect are kulture ye fenion basterb */ +#define FLAGS_ARESET(var, flegs) \ +((var & (flegs)) == (flegs)) + +#define FLAGS_SET(var, flegs) \ +(var |= flegs) + +#define FLAGS_UNSET(var, flegs) \ +(var &= ~flegs) + +#define TIMINGSERVICE_OPERATIONAL 0x01 /* functioning */ +#define TIMINGSERVICE_AVAILABLE 0x02 /* ready to control the clock */ +#define TIMINGSERVICE_IN_CONTROL 0x04 /* allowed to control the clock - has control */ +#define TIMINGSERVICE_IDLE 0x08 /* not showing clock activity */ +#define TIMINGSERVICE_HOLD 0x10 /* hold timer running */ +#define TIMINGSERVICE_SINK_ONLY 0x20 /* only servers time */ +#define TIMINGSERVICE_NO_TOD 0x40 /* does not provide time of day */ + +#define MAX_TIMINGSERVICES 16 +/* #define MAX_CLOCKSOURCES 16 */ + +#define TIMINGSERVICE_MAX_DESC 20 + +enum { + REASON_NONE, + REASON_IDLE, + REASON_ELECTION, + REASON_CTRL_NOT_BEST, + REASON_ELIGIBLE +}; + +typedef enum { + TIMINGSERVICE_NONE = 0x00, + TIMINGSERVICE_PTP = 0x10, + TIMINGSERVICE_PPS = 0x20, + TIMINGSERVICE_GPS = 0x30, + TIMINGSERVICE_NTP = 0x40, + TIMINGSERVICE_OTHER = 0xFE, + TIMINGSERVICE_MAX = 0xFF +} +TimingServiceType; + +typedef struct { + uint8_t priority1; + TimingServiceType type; + uint8_t priority2; +} +TimingServiceDS; + +typedef struct TimingService TimingService; +typedef struct TimingDomain TimingDomain; + +struct TimingService { + TimingServiceDS dataSet; + char id[TIMINGSERVICE_MAX_DESC+1]; /* description like PTP-eth0 */ + uint8_t flags; /* see top of file */ + int updateInterval;/* minimum updatwe interval: some services need to be checked less frequently */ + int lastUpdate; /* seconds since last update */ + int minIdleTime; /* minumum and idle time. If we sync every 5 minutes, 2 minute timeout does not make much sense */ + int idleTime; /* idle time - how long have we not had updated the clock */ + int timeout; /* time between going idle and handing over clock control to next best */ + int holdTime; /* how long to hold clock control when stopped controlling the clock */ + int holdTimeLeft; /* how long to hold clock control when stopped controlling the clock */ + Boolean updateDue; /* used internally by TimingDomain: should be private... */ + Boolean activity; /* periodically updated by the time service, used to detect idle state */ + Boolean reloadRequested; /* WHO WANTS A RELOAD!!! */ + Boolean restartRequested; /* Service needs restarted */ + Boolean released; /* so that we don't issue messages twice */ + void *controller; /* the object behind the TimingService - such as PTPClock, NTPClock etc. */ + void *config; /* configuration object used by the controller - RunTimeOpts, NTPOptions etc. */ + TimingDomain *parent; /* pointer to the TimingDomain this service belongs to */ +/* ClockSource clockSource */ /* initially there is only the kernel clock... */ + int (*init) (TimingService *service); /* init method that the service can assign */ + int (*shutdown) (TimingService *service); /* shutdown method that the service can assign */ + int (*acquire) (TimingService *service); /* method called when the given service should acquire clock control */ + int (*release) (TimingService *service, int reason); /* method called when the given service should release clock control */ + int (*update) (TimingService *service); /* heartbeat - called to allow the service to report if it's operational */ + int (*clockUpdate) (TimingService *service); /* allows the service to inform the clock source about things like sync status, UTC offset, etc */ +}; + +struct TimingDomain { + TimingService *services[MAX_TIMINGSERVICES]; /* temporary. this should be dynamic in future */ + TimingService *current; /* pointer to the currently used service - null before election */ + TimingService *best; /* pointer to the winning service (best out of all active) */ + TimingService *preferred; /* naturally preferred = best looking at properties only */ + + /* ClockSource *sources[MAX_CLOCKSOURCES];*/ /* eventually */ + int serviceCount; + /* TimingService *best[MAX_CLOCKSOURCES]; */ /* eventually, best service for given clock source */ + + int updateInterval; /* this is so TimingDomain knows how often it's being checked */ + Boolean noneAvailable; /* used so that the no available warning is not repeated */ + + + /* Counters */ + int availableCount; + int operationalCount; + int idleCount; + int controlCount; + + /* used to prevent flapping: when best is released or better exists, wait first */ + + /* the delay */ + int electionDelay; + /* the countdown counter */ + int electionLeft; + + int (*init) (TimingDomain *domain); /* init method */ + int (*shutdown) (TimingDomain *domain); /* shutdown method */ + int (*update) (TimingDomain *domain); /* main update call */ +}; + +int timingDomainSetup(TimingDomain *domain); +int timingServiceSetup(TimingService *service); + +extern TimingDomain timingDomain; + +#endif /* TIMINGDOMAIN_H_ */ diff --git a/src/ptpd/test/client-e2e-8023.conf b/src/ptpd/test/client-e2e-8023.conf new file mode 100644 index 0000000..fec3dde --- /dev/null +++ b/src/ptpd/test/client-e2e-8023.conf @@ -0,0 +1,447 @@ +; ======================================== +; PTPDv2 version 2.3.0 802.3 client configuration (requires PCAP) +; ======================================== + +; NOTE: the following settings are affected by ptpengine:preset selection: +; ptpengine:slave_only +; clock:no_adjust +; ptpengine:clock_class - allowed range and default value +; To see all preset settings, run ptpd2 -H (--long-help) + +; Network interface to use (required) +ptpengine:interface = + +; PTP engine preset: +; none = Defaults, no clock class restrictions +; slaveonly = Slave only (clock class 255 only) +; masteronly = Master, passive when not best master (clock class 0..127) +; masterslave = Full IEEE 1588 implementation: +; Master, slave when not best master +; (clock class 128..254) +; +; Options: none slaveonly masteronly masterslave +ptpengine:preset = slaveonly + +; IP transmission mode (requires IP transport) - hybrid mode uses +; multicast for sync and announce, and unicast for delay request / +; response +; Options: multicast unicast hybrid +;ptpengine:ip_mode = multicast + +; Transport type for PTP packets +; Options: ipv4 ethernet +ptpengine:transport = ethernet + +; Use libpcap for sending and receiving traffic (automatically enabled +; in Ethernet mode) +ptpengine:use_libpcap = N + +; Delay detection mode used - use DELAY_DISABLED for syntonisation +; only (no synchronisation) +; Options: E2E P2P DELAY_DISABLED +ptpengine:delay_mechanism = E2E + +; PTP domain number +ptpengine:domain = 0 + +; Slave only mode (if set, overrides preset setting and sets clock class to 255) +ptpengine:slave_only = Y + +; Specify latency correction for incoming packets +ptpengine:inbound_latency = 0 + +; Specify latency correction for outgoing packets +ptpengine:outbound_latency = 0 + +; Compatibility option: In slave state, always respect UTC offset +; announced by best master, even if the the +; currrentUtcOffsetValid flag is announced FALSE +ptpengine:always_respect_utc_offset = N + +; PTP announce message interval in master state (expressed as log 2 +; i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_announce_interval = 1 + +; PTP announce receipt timeout announced in master state +ptpengine:announce_timeout = 6 + +; PTP announce receipt timeout grace period in slave state: +; when announce receipt timeout occurs, disqualify current best GM, +; then wait n times announce receipt timeout before resetting. +; Allows for a seamless GM failover when standby GMs are slow to react. +; When set to 0, this option is not used. +ptpengine:announce_timeout_grace_period = 0 + +; PTP sync message interval in master state (expressed as log 2 +; i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_sync_interval = 0 + +; Initial delay request message interval for slave mode, before first +; delay response is received (expressed as log 2 i.e. -1=0.5s, 0=1s, +; 1=2s etc.) +ptpengine:log_delayreq_interval_initial = 0 + +; Minimum delay request message interval in master state, in slave +; mode overrides the master interval, required in hybrid mode +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_delayreq_interval = 0 + +; Minimum peer delay request message interval in master state. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_peer_delayreq_interval = 1 + +; Maximum number of foreign masters (foreign master record size +; allocated at startup) + +ptpengine:foreignrecord_capacity = 5 + +; Specify Allan variance announced in master state +ptpengine:ptp_allan_variance = 28768 + +; Clock accuracy range announced in master state +; Options: ACC_25NS ACC_100NS ACC_250NS ACC_1US ACC_2.5US ACC_10US +; ACC_25US ACC_100US ACC_250US ACC_1MS ACC_2.5MS ACC_10MS ACC_25MS +; ACC_100MS ACC_250MS ACC_1S ACC_10S ACC_10SPLUS ACC_UNKNOWN +ptpengine:ptp_clock_accuracy = ACC_UNKNOWN + +; underlying time source UTC offset announced in master state +ptpengine:utc_offset = 0 + +; underlying time source UTC offset validity announced in master state +ptpengine:utc_offset_valid = N + +; underlying time source time traceability announced in master state +ptpengine:time_traceable = N + +; underlying time source frequency traceability announced in master state +ptpengine:frequency_traceable = N + +; Time scale announced in master state (with ARB timescale, UTC +; properties are ignored by slaves), when clock class 13 (application +; specific), this value is ignored and ARB is used. +; Options: PTP ARB +ptpengine:ptp_timescale = ARB + +; Time source announced in master state +; Options: ATOMIC_CLOCK GPS TERRESTRIAL_RADIO PTP NTP HAND_SET OTHER +; INTERNAL_OSCILLATOR +ptpengine:ptp_timesource = INTERNAL_OSCILLATOR + +; Clock class - announced in master state. Always 255 for slave-only mode. +; Minimum, maximum and default values are controlled by presets. +; If set to 13 (application specific time source), announced +; time scale is always set to ARB. This setting controls the +; states a PTP port can be in. If below 128, port will only +; be in MASTER or PASSIVE states (master only). If above 127, +; port will be in MASTER or SLAVE states. +ptpengine:clock_class = 255 + +; Priority 1 value announced in master state and used for Best Master +; Clock selection +ptpengine:priority1 = 128 + +; Priority 2 value announced in master state and used for Best Master +; Clock selection +ptpengine:priority2 = 128 + +; Specify unicast destination for unicast master mode (in unicast +; slave mode overrides delay request destination) +ptpengine:unicast_address = + +; Send explicit IGMP joins between servo resets +ptpengine:igmp_refresh = Y + +; Multicast time to live for multicast PTP packets (ignored and set to +; 1 for peer to peer messages) +ptpengine:multicast_ttl = 64 + +; DiffServ CodepPoint for packet prioritisation (decimal). When set to +; zero, this option is not used. +; 46 = Expedited Forwarding (0x2e) +ptpengine:ip_dscp = 0 + +; Enable outlier filter for the Delay Response component in slave state +ptpengine:delay_outlier_filter_enable = N + +; Delay Response outlier filter action. If set to 'filter', outliers +; are replaced with moving average +; Options: discard filter +ptpengine:delay_outlier_filter_action = filter + +; Number of samples in the Delay Response outlier filter buffer +ptpengine:delay_outlier_filter_capacity = 20 + +; Delay Response outlier filter threshold: multiplier for the Peirce's +; maximum standard deviation. When set below 1.0, filter is tighter, +; when set above 1.0, filter is looser than standard Peirce's test. +ptpengine:delay_outlier_filter_threshold = 1.000000 + +; Delay Response outlier weight: if an outlier is detected, this value +; determines the amount of its deviation from mean that is used to +; build the standard deviation statistics and influence further +; outlier detection. +; When set to 1.0, the outlier is used as is. +; +ptpengine:delay_outlier_weight = 1.000000 + +; Enable outlier filter for the Sync component in slave state +ptpengine:sync_outlier_filter_enable = N + +; Sync outlier filter action. If set to 'filter', outliers are +; replaced with moving average +; Options: discard filter +ptpengine:sync_outlier_filter_action = filter + +; Number of samples in the Sync outlier filter buffer +ptpengine:sync_outlier_filter_capacity = 20 + +; Sync outlier filter threshold: multiplier for the Peirce's maximum +; standard deviation. When set below 1.0, filter is tighter, when set +; above 1.0, filter is looser than standard Peirce's test. +ptpengine:sync_outlier_filter_threshold = 1.000000 + +; Sync outlier weight: if an outlier is detected, this value +; determines the amount of its deviation from mean that is used to +; build the standard deviation statistics and influence further +; outlier detection. When set to 1.0, the outlier is used as is. +ptpengine:sync_outlier_weight = 1.000000 + +; Delay between moving to slave state and enabling clock updates +; expressed as number of statistics update periods (see +; global:statistics_update_interval). This allows one-way delay to +; stabilise before starting clock updates. Activated when going into +; slave state and during GM failover in slave state. +; 0 - not used. +ptpengine:calibration_delay = 0 + +; Enable panic mode: when offset from master is above 1 second, stop +; updating the clock for a period of time and then step the clock if +; offset remains above 1 second. +ptpengine:panic_mode = N + +; Duration of the panic mode period (no clock updates) when offset +; above 1 second detected +ptpengine:panic_mode_duration = 2 + +; Use JobID (PID) for UUID +ptpengine:pid_as_clock_idendity = N + +; Fail over to NTP when PTP time sync not available - requires +; ntpengine:enabled but does not require the rest of NTP configuration +; - will warn instead of failing over if cannot control ntpd. +ptpengine:ntp_failover = N + +; NTP failover timeout in seconds: time between PTP slave going into +; LISTENING state, and failing over to NTP. 0 = fail over immediately. +ptpengine:ntp_failover_timeout = 60 + +; Prefer NTP time synchronisation when not controlling the clock (all +; states, including slave when clock:no_adjust set) +ptpengine:prefer_ntp = N + +; When entering panic mode, fail over to NTP (after the NTP failover +; timeout period) - requires ntpengine:enabled but does not require +; the rest of NTP configuration - will warn instead of failing over if +; it cannot control ntpd. +ptpengine:panic_mode_ntp = N + +; Do not adjust the clock +clock:no_adjust = N + +; Do not reset the clock - only slew +clock:no_reset = N + +; Observed drift handling method between servo restarts: +; reset: set to zero (not recommended) +; preserve: use kernel value, +; file: load and save to drift file on startup/shutdown, use kernel +; value inbetween. +; To specify drift file, use the clock:drift_file setting. +; Options: reset preserve file +clock:drift_handling = preserve + +; Specify drift file +clock:drift_file = /etc/ptpd2_kernelclock.drift + +; Maximum absolute frequency shift which can be applied to the clock servo +; when slewing the clock. Expressed in parts per million (1 ppm = shift of +; 1 us per second. Values above 512 will use the tick duration correction +; to allow even faster slewing. Default maximum is 512 without using tick. +clock:max_offset_ppm = 500 + +; One-way delay filter stiffness +servo:delayfilter_stiffness = 6 + +; Clock servo PI controller proportional component gain (kP) +servo:kp = 0.1 + +; Clock servo PI controller integral component gain (kI) +servo:ki = 0.001 + +; Maximum accepted delayMS value in nanoseconds (Sync). +; 0 = not checked. +servo:max_delay = 0 + +; Enable clock synchronisation servo stability detection +; (based on standard deviation of the observed drift value) +; - drift will be saved to drift file / cached when considered stable, +; also clock stability status will be logged +; +servo:stability_detection = N + +; Specify the observed drift standard deviation threshold in parts per billion +; (ppb) - if stanard deviation is within the threshold, servo is considered +; stable. +servo:stability_threshold = 5.000000 + +; Specify for how many statistics update intervals the observed drift standard +; deviation has to stay within threshold to be considered stable +; +servo:stability_period = 3 + +; Specify after how many minutes without stabilisation servo is considered +; unstable. Assists with logging servo stability information and +; allows to preserve observed drift if servo cannot stabilise. +; +servo:stability_timeout = 10 + +; Do not update one-way delay if slave to master delay (from Delay Response) +; is greater than this value (nanoseconds). 0 = not used. +servo:max_delay = 0 + +; Do not reset the clock if offset from master is greater +; than this value (nanoseconds). 0 = not used. +servo:max_offset = 0 + +; Send log messages to syslog. Disabling this +; sends all messages to stdout (or speficied log file) +global:use_syslog = N + +; Lock file location +global:lock_file = + +; Use mode specific and interface specific lock files (overrides +; global:lock_file) +global:auto_lockfile = N + +; Lock file directory: used with automatic mode-specific lock files, +; also used when no lock file is specified. When lock file +; is specified, it's expected to be an absolute path. +global:lock_directory = /var/run + +; Skip lock file checking and locking +global:ignore_lock = N + +; File used to record data about sync packets. Setting this enables recording. +global:quality_file = + +; Maximum sync packet record file size (in kB) - file will be +; truncated if size exceeds the limit. +; 0 - no limit. +global:quality_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate. +global:quality_file_max_files = 0 + +; Truncate the sync packet record file every time it is (re) opened - +; on startup and SIGHUP +global:quality_file_truncate = N + +; File used to log ptpd2 status information +global:status_file = /var/run/ptpd2.status.log + +; Enable / disable writing status information to file +global:log_status = Y + +; Status file update interval in seconds +; +global:status_update_interval = 1 + +; Specify log file path (event log). Setting this enables logging to file. +global:log_file = /var/run/ptpd2.event.log + +; Maximum log file size (in kB) - log file will be truncated if size +; exceeds the limit. +; 0 - no limit. +global:log_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate +global:log_file_max_files = 0 + +; Truncate the log file every time it is (re) opened - on startup and SIGHUP +global:log_file_truncate = N + +; Specify log level (only messages of the specified priority or higer +; will be logged). +; The minimal level is LOG_ERR. LOG_ALL enables debug output if compiled with +; RUNTIME_DEBUG +; Options: LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_ALL +global:log_level = LOG_ALL + +; Specify statistics log file path. Setting this enables logging of +; statistics but can be overriden with global:log_statistics +global:statistics_file = /var/run/ptpd2.stats.log + +; Log timing statistics every n seconds for Sync and Delay Response +; messages (0 - log all) +global:statistics_log_interval = 0 + +; Maximum statistics log file size (in kB) - log file will be +; truncated if size exceeds the limit. +; 0 - no limit. +global:statistics_file_max_size = 0 + +; Enable log rotation of the statistics file up to n files. 0 - do not rotate +; +global:statistics_file_max_files = 0 + +; Truncate the statistics file every time it is (re) opened - on +; startup and SIGHUP +global:statistics_file_truncate = N + +; Dump the contents of every PTP packet +global:dump_packets = N + +; Run in foreground with statistics and all messages logged to stdout. +; Overrides log file and statistics file settings and disables syslog. +; +global:verbose_foreground = N + +; Run in foreground +global:foreground = N + +; Log timing statistics for every PTP packet received +global:log_statistics = Y + +; Linux only: bind ptpd2 process to a selected CPU core number. +; 0 = first CPU core, etc. -1 = do not bind to a single core. +global:cpuaffinity_cpucore = -1 + +; Clock synchronisation statistics update interval in seconds +; +global:statistics_update_interval = 5 + +; Enable NTPd integration +ntpengine:enabled = N + +; Enable control over local NTPd daemon +ntpengine:control_enabled = N + +; NTP control check interval in seconds +; +ntpengine:check_interval = 15 + +; NTP key number - must be configured as a trusted control key in ntp.conf, +; and must be non-zero for the ntpengine:control_enabled setting to take effect. +; +ntpengine:key_id = 0 + +; NTP key (plain text, max. 20 characters) - must match the key +; configured in ntpd's keys file, and must be non-zero for the +; ntpengine:control_enabled setting to take effect. +ntpengine:key = + +; ========= newline required in the end ========== + diff --git a/src/ptpd/test/client-e2e-pcap.conf b/src/ptpd/test/client-e2e-pcap.conf new file mode 100644 index 0000000..8b47fc5 --- /dev/null +++ b/src/ptpd/test/client-e2e-pcap.conf @@ -0,0 +1,447 @@ +; ======================================== +; PTPDv2 version 2.3.0 PCAP + IPv4 client configuration +; ======================================== + +; NOTE: the following settings are affected by ptpengine:preset selection: +; ptpengine:slave_only +; clock:no_adjust +; ptpengine:clock_class - allowed range and default value +; To see all preset settings, run ptpd2 -H (--long-help) + +; Network interface to use (required) +ptpengine:interface = + +; PTP engine preset: +; none = Defaults, no clock class restrictions +; slaveonly = Slave only (clock class 255 only) +; masteronly = Master, passive when not best master (clock class 0..127) +; masterslave = Full IEEE 1588 implementation: +; Master, slave when not best master +; (clock class 128..254) +; +; Options: none slaveonly masteronly masterslave +ptpengine:preset = slaveonly + +; IP transmission mode (requires IP transport) - hybrid mode uses +; multicast for sync and announce, and unicast for delay request / +; response +; Options: multicast unicast hybrid +ptpengine:ip_mode = multicast + +; Transport type for PTP packets +; Options: ipv4 ethernet +ptpengine:transport = ipv4 + +; Use libpcap for sending and receiving traffic (automatically enabled +; in Ethernet mode) +ptpengine:use_libpcap = Y + +; Delay detection mode used - use DELAY_DISABLED for syntonisation +; only (no synchronisation) +; Options: E2E P2P DELAY_DISABLED +ptpengine:delay_mechanism = E2E + +; PTP domain number +ptpengine:domain = 0 + +; Slave only mode (if set, overrides preset setting and sets clock class to 255) +ptpengine:slave_only = Y + +; Specify latency correction for incoming packets +ptpengine:inbound_latency = 0 + +; Specify latency correction for outgoing packets +ptpengine:outbound_latency = 0 + +; Compatibility option: In slave state, always respect UTC offset +; announced by best master, even if the the +; currrentUtcOffsetValid flag is announced FALSE +ptpengine:always_respect_utc_offset = N + +; PTP announce message interval in master state (expressed as log 2 +; i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_announce_interval = 1 + +; PTP announce receipt timeout announced in master state +ptpengine:announce_timeout = 6 + +; PTP announce receipt timeout grace period in slave state: +; when announce receipt timeout occurs, disqualify current best GM, +; then wait n times announce receipt timeout before resetting. +; Allows for a seamless GM failover when standby GMs are slow to react. +; When set to 0, this option is not used. +ptpengine:announce_timeout_grace_period = 0 + +; PTP sync message interval in master state (expressed as log 2 +; i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_sync_interval = 0 + +; Initial delay request message interval for slave mode, before first +; delay response is received (expressed as log 2 i.e. -1=0.5s, 0=1s, +; 1=2s etc.) +ptpengine:log_delayreq_interval_initial = 0 + +; Minimum delay request message interval in master state, in slave +; mode overrides the master interval, required in hybrid mode +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_delayreq_interval = 0 + +; Minimum peer delay request message interval in master state. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_peer_delayreq_interval = 1 + +; Maximum number of foreign masters (foreign master record size +; allocated at startup) + +ptpengine:foreignrecord_capacity = 5 + +; Specify Allan variance announced in master state +ptpengine:ptp_allan_variance = 28768 + +; Clock accuracy range announced in master state +; Options: ACC_25NS ACC_100NS ACC_250NS ACC_1US ACC_2.5US ACC_10US +; ACC_25US ACC_100US ACC_250US ACC_1MS ACC_2.5MS ACC_10MS ACC_25MS +; ACC_100MS ACC_250MS ACC_1S ACC_10S ACC_10SPLUS ACC_UNKNOWN +ptpengine:ptp_clock_accuracy = ACC_UNKNOWN + +; underlying time source UTC offset announced in master state +ptpengine:utc_offset = 0 + +; underlying time source UTC offset validity announced in master state +ptpengine:utc_offset_valid = N + +; underlying time source time traceability announced in master state +ptpengine:time_traceable = N + +; underlying time source frequency traceability announced in master state +ptpengine:frequency_traceable = N + +; Time scale announced in master state (with ARB timescale, UTC +; properties are ignored by slaves), when clock class 13 (application +; specific), this value is ignored and ARB is used. +; Options: PTP ARB +ptpengine:ptp_timescale = ARB + +; Time source announced in master state +; Options: ATOMIC_CLOCK GPS TERRESTRIAL_RADIO PTP NTP HAND_SET OTHER +; INTERNAL_OSCILLATOR +ptpengine:ptp_timesource = INTERNAL_OSCILLATOR + +; Clock class - announced in master state. Always 255 for slave-only mode. +; Minimum, maximum and default values are controlled by presets. +; If set to 13 (application specific time source), announced +; time scale is always set to ARB. This setting controls the +; states a PTP port can be in. If below 128, port will only +; be in MASTER or PASSIVE states (master only). If above 127, +; port will be in MASTER or SLAVE states. +ptpengine:clock_class = 255 + +; Priority 1 value announced in master state and used for Best Master +; Clock selection +ptpengine:priority1 = 128 + +; Priority 2 value announced in master state and used for Best Master +; Clock selection +ptpengine:priority2 = 128 + +; Specify unicast destination for unicast master mode (in unicast +; slave mode overrides delay request destination) +ptpengine:unicast_address = + +; Send explicit IGMP joins between servo resets +ptpengine:igmp_refresh = Y + +; Multicast time to live for multicast PTP packets (ignored and set to +; 1 for peer to peer messages) +ptpengine:multicast_ttl = 64 + +; DiffServ CodepPoint for packet prioritisation (decimal). When set to +; zero, this option is not used. +; 46 = Expedited Forwarding (0x2e) +ptpengine:ip_dscp = 0 + +; Enable outlier filter for the Delay Response component in slave state +ptpengine:delay_outlier_filter_enable = N + +; Delay Response outlier filter action. If set to 'filter', outliers +; are replaced with moving average +; Options: discard filter +ptpengine:delay_outlier_filter_action = filter + +; Number of samples in the Delay Response outlier filter buffer +ptpengine:delay_outlier_filter_capacity = 20 + +; Delay Response outlier filter threshold: multiplier for the Peirce's +; maximum standard deviation. When set below 1.0, filter is tighter, +; when set above 1.0, filter is looser than standard Peirce's test. +ptpengine:delay_outlier_filter_threshold = 1.000000 + +; Delay Response outlier weight: if an outlier is detected, this value +; determines the amount of its deviation from mean that is used to +; build the standard deviation statistics and influence further +; outlier detection. +; When set to 1.0, the outlier is used as is. +; +ptpengine:delay_outlier_weight = 1.000000 + +; Enable outlier filter for the Sync component in slave state +ptpengine:sync_outlier_filter_enable = N + +; Sync outlier filter action. If set to 'filter', outliers are +; replaced with moving average +; Options: discard filter +ptpengine:sync_outlier_filter_action = filter + +; Number of samples in the Sync outlier filter buffer +ptpengine:sync_outlier_filter_capacity = 20 + +; Sync outlier filter threshold: multiplier for the Peirce's maximum +; standard deviation. When set below 1.0, filter is tighter, when set +; above 1.0, filter is looser than standard Peirce's test. +ptpengine:sync_outlier_filter_threshold = 1.000000 + +; Sync outlier weight: if an outlier is detected, this value +; determines the amount of its deviation from mean that is used to +; build the standard deviation statistics and influence further +; outlier detection. When set to 1.0, the outlier is used as is. +ptpengine:sync_outlier_weight = 1.000000 + +; Delay between moving to slave state and enabling clock updates +; expressed as number of statistics update periods (see +; global:statistics_update_interval). This allows one-way delay to +; stabilise before starting clock updates. Activated when going into +; slave state and during GM failover in slave state. +; 0 - not used. +ptpengine:calibration_delay = 0 + +; Enable panic mode: when offset from master is above 1 second, stop +; updating the clock for a period of time and then step the clock if +; offset remains above 1 second. +ptpengine:panic_mode = N + +; Duration of the panic mode period (no clock updates) when offset +; above 1 second detected +ptpengine:panic_mode_duration = 2 + +; Use JobID (PID) for UUID +ptpengine:pid_as_clock_idendity = N + +; Fail over to NTP when PTP time sync not available - requires +; ntpengine:enabled but does not require the rest of NTP configuration +; - will warn instead of failing over if cannot control ntpd. +ptpengine:ntp_failover = N + +; NTP failover timeout in seconds: time between PTP slave going into +; LISTENING state, and failing over to NTP. 0 = fail over immediately. +ptpengine:ntp_failover_timeout = 60 + +; Prefer NTP time synchronisation when not controlling the clock (all +; states, including slave when clock:no_adjust set) +ptpengine:prefer_ntp = N + +; When entering panic mode, fail over to NTP (after the NTP failover +; timeout period) - requires ntpengine:enabled but does not require +; the rest of NTP configuration - will warn instead of failing over if +; it cannot control ntpd. +ptpengine:panic_mode_ntp = N + +; Do not adjust the clock +clock:no_adjust = N + +; Do not reset the clock - only slew +clock:no_reset = N + +; Observed drift handling method between servo restarts: +; reset: set to zero (not recommended) +; preserve: use kernel value, +; file: load and save to drift file on startup/shutdown, use kernel +; value inbetween. +; To specify drift file, use the clock:drift_file setting. +; Options: reset preserve file +clock:drift_handling = preserve + +; Specify drift file +clock:drift_file = /etc/ptpd2_kernelclock.drift + +; Maximum absolute frequency shift which can be applied to the clock servo +; when slewing the clock. Expressed in parts per million (1 ppm = shift of +; 1 us per second. Values above 512 will use the tick duration correction +; to allow even faster slewing. Default maximum is 512 without using tick. +clock:max_offset_ppm = 500 + +; One-way delay filter stiffness +servo:delayfilter_stiffness = 6 + +; Clock servo PI controller proportional component gain (kP) +servo:kp = 0.1 + +; Clock servo PI controller integral component gain (kI) +servo:ki = 0.001 + +; Maximum accepted delayMS value in nanoseconds (Sync). +; 0 = not checked. +servo:max_delay = 0 + +; Enable clock synchronisation servo stability detection +; (based on standard deviation of the observed drift value) +; - drift will be saved to drift file / cached when considered stable, +; also clock stability status will be logged +; +servo:stability_detection = N + +; Specify the observed drift standard deviation threshold in parts per billion +; (ppb) - if stanard deviation is within the threshold, servo is considered +; stable. +servo:stability_threshold = 5.000000 + +; Specify for how many statistics update intervals the observed drift standard +; deviation has to stay within threshold to be considered stable +; +servo:stability_period = 3 + +; Specify after how many minutes without stabilisation servo is considered +; unstable. Assists with logging servo stability information and +; allows to preserve observed drift if servo cannot stabilise. +; +servo:stability_timeout = 10 + +; Do not update one-way delay if slave to master delay (from Delay Response) +; is greater than this value (nanoseconds). 0 = not used. +servo:max_delay = 0 + +; Do not reset the clock if offset from master is greater +; than this value (nanoseconds). 0 = not used. +servo:max_offset = 0 + +; Send log messages to syslog. Disabling this +; sends all messages to stdout (or speficied log file) +global:use_syslog = N + +; Lock file location +global:lock_file = + +; Use mode specific and interface specific lock files (overrides +; global:lock_file) +global:auto_lockfile = N + +; Lock file directory: used with automatic mode-specific lock files, +; also used when no lock file is specified. When lock file +; is specified, it's expected to be an absolute path. +global:lock_directory = /var/run + +; Skip lock file checking and locking +global:ignore_lock = N + +; File used to record data about sync packets. Setting this enables recording. +global:quality_file = + +; Maximum sync packet record file size (in kB) - file will be +; truncated if size exceeds the limit. +; 0 - no limit. +global:quality_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate. +global:quality_file_max_files = 0 + +; Truncate the sync packet record file every time it is (re) opened - +; on startup and SIGHUP +global:quality_file_truncate = N + +; File used to log ptpd2 status information +global:status_file = /var/run/ptpd2.status.log + +; Enable / disable writing status information to file +global:log_status = Y + +; Status file update interval in seconds +; +global:status_update_interval = 1 + +; Specify log file path (event log). Setting this enables logging to file. +global:log_file = /var/run/ptpd2.event.log + +; Maximum log file size (in kB) - log file will be truncated if size +; exceeds the limit. +; 0 - no limit. +global:log_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate +global:log_file_max_files = 0 + +; Truncate the log file every time it is (re) opened - on startup and SIGHUP +global:log_file_truncate = N + +; Specify log level (only messages of the specified priority or higer +; will be logged). +; The minimal level is LOG_ERR. LOG_ALL enables debug output if compiled with +; RUNTIME_DEBUG +; Options: LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_ALL +global:log_level = LOG_ALL + +; Specify statistics log file path. Setting this enables logging of +; statistics but can be overriden with global:log_statistics +global:statistics_file = /var/run/ptpd2.stats.log + +; Log timing statistics every n seconds for Sync and Delay Response +; messages (0 - log all) +global:statistics_log_interval = 0 + +; Maximum statistics log file size (in kB) - log file will be +; truncated if size exceeds the limit. +; 0 - no limit. +global:statistics_file_max_size = 0 + +; Enable log rotation of the statistics file up to n files. 0 - do not rotate +; +global:statistics_file_max_files = 0 + +; Truncate the statistics file every time it is (re) opened - on +; startup and SIGHUP +global:statistics_file_truncate = N + +; Dump the contents of every PTP packet +global:dump_packets = N + +; Run in foreground with statistics and all messages logged to stdout. +; Overrides log file and statistics file settings and disables syslog. +; +global:verbose_foreground = N + +; Run in foreground +global:foreground = N + +; Log timing statistics for every PTP packet received +global:log_statistics = Y + +; Linux only: bind ptpd2 process to a selected CPU core number. +; 0 = first CPU core, etc. -1 = do not bind to a single core. +global:cpuaffinity_cpucore = -1 + +; Clock synchronisation statistics update interval in seconds +; +global:statistics_update_interval = 5 + +; Enable NTPd integration +ntpengine:enabled = N + +; Enable control over local NTPd daemon +ntpengine:control_enabled = N + +; NTP control check interval in seconds +; +ntpengine:check_interval = 15 + +; NTP key number - must be configured as a trusted control key in ntp.conf, +; and must be non-zero for the ntpengine:control_enabled setting to take effect. +; +ntpengine:key_id = 0 + +; NTP key (plain text, max. 20 characters) - must match the key +; configured in ntpd's keys file, and must be non-zero for the +; ntpengine:control_enabled setting to take effect. +ntpengine:key = + +; ========= newline required in the end ========== + diff --git a/src/ptpd/test/client-e2e-socket.conf b/src/ptpd/test/client-e2e-socket.conf new file mode 100644 index 0000000..91f52c6 --- /dev/null +++ b/src/ptpd/test/client-e2e-socket.conf @@ -0,0 +1,447 @@ +; ======================================== +; PTPDv2 version 2.3.0-svn default configuration +; ======================================== + +; NOTE: the following settings are affected by ptpengine:preset selection: +; ptpengine:slave_only +; clock:no_adjust +; ptpengine:clock_class - allowed range and default value +; To see all preset settings, run ptpd2 -H (--long-help) + +; Network interface to use (required) +ptpengine:interface = + +; PTP engine preset: +; none = Defaults, no clock class restrictions +; slaveonly = Slave only (clock class 255 only) +; masteronly = Master, passive when not best master (clock class 0..127) +; masterslave = Full IEEE 1588 implementation: +; Master, slave when not best master +; (clock class 128..254) +; +; Options: none slaveonly masteronly masterslave +ptpengine:preset = slaveonly + +; IP transmission mode (requires IP transport) - hybrid mode uses +; multicast for sync and announce, and unicast for delay request / +; response +; Options: multicast unicast hybrid +ptpengine:ip_mode = multicast + +; Transport type for PTP packets +; Options: ipv4 ethernet +ptpengine:transport = ipv4 + +; Use libpcap for sending and receiving traffic (automatically enabled +; in Ethernet mode) +ptpengine:use_libpcap = N + +; Delay detection mode used - use DELAY_DISABLED for syntonisation +; only (no synchronisation) +; Options: E2E P2P DELAY_DISABLED +ptpengine:delay_mechanism = E2E + +; PTP domain number +ptpengine:domain = 0 + +; Slave only mode (if set, overrides preset setting and sets clock class to 255) +ptpengine:slave_only = Y + +; Specify latency correction for incoming packets +ptpengine:inbound_latency = 0 + +; Specify latency correction for outgoing packets +ptpengine:outbound_latency = 0 + +; Compatibility option: In slave state, always respect UTC offset +; announced by best master, even if the the +; currrentUtcOffsetValid flag is announced FALSE +ptpengine:always_respect_utc_offset = N + +; PTP announce message interval in master state (expressed as log 2 +; i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_announce_interval = 1 + +; PTP announce receipt timeout announced in master state +ptpengine:announce_timeout = 6 + +; PTP announce receipt timeout grace period in slave state: +; when announce receipt timeout occurs, disqualify current best GM, +; then wait n times announce receipt timeout before resetting. +; Allows for a seamless GM failover when standby GMs are slow to react. +; When set to 0, this option is not used. +ptpengine:announce_timeout_grace_period = 0 + +; PTP sync message interval in master state (expressed as log 2 +; i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_sync_interval = 0 + +; Initial delay request message interval for slave mode, before first +; delay response is received (expressed as log 2 i.e. -1=0.5s, 0=1s, +; 1=2s etc.) +ptpengine:log_delayreq_interval_initial = 0 + +; Minimum delay request message interval in master state, in slave +; mode overrides the master interval, required in hybrid mode +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_delayreq_interval = 0 + +; Minimum peer delay request message interval in master state. +; (expressed as log 2 i.e. -1=0.5s, 0=1s, 1=2s etc.) +ptpengine:log_peer_delayreq_interval = 1 + +; Maximum number of foreign masters (foreign master record size +; allocated at startup) + +ptpengine:foreignrecord_capacity = 5 + +; Specify Allan variance announced in master state +ptpengine:ptp_allan_variance = 28768 + +; Clock accuracy range announced in master state +; Options: ACC_25NS ACC_100NS ACC_250NS ACC_1US ACC_2.5US ACC_10US +; ACC_25US ACC_100US ACC_250US ACC_1MS ACC_2.5MS ACC_10MS ACC_25MS +; ACC_100MS ACC_250MS ACC_1S ACC_10S ACC_10SPLUS ACC_UNKNOWN +ptpengine:ptp_clock_accuracy = ACC_UNKNOWN + +; underlying time source UTC offset announced in master state +ptpengine:utc_offset = 0 + +; underlying time source UTC offset validity announced in master state +ptpengine:utc_offset_valid = N + +; underlying time source time traceability announced in master state +ptpengine:time_traceable = N + +; underlying time source frequency traceability announced in master state +ptpengine:frequency_traceable = N + +; Time scale announced in master state (with ARB timescale, UTC +; properties are ignored by slaves), when clock class 13 (application +; specific), this value is ignored and ARB is used. +; Options: PTP ARB +ptpengine:ptp_timescale = ARB + +; Time source announced in master state +; Options: ATOMIC_CLOCK GPS TERRESTRIAL_RADIO PTP NTP HAND_SET OTHER +; INTERNAL_OSCILLATOR +ptpengine:ptp_timesource = INTERNAL_OSCILLATOR + +; Clock class - announced in master state. Always 255 for slave-only mode. +; Minimum, maximum and default values are controlled by presets. +; If set to 13 (application specific time source), announced +; time scale is always set to ARB. This setting controls the +; states a PTP port can be in. If below 128, port will only +; be in MASTER or PASSIVE states (master only). If above 127, +; port will be in MASTER or SLAVE states. +ptpengine:clock_class = 255 + +; Priority 1 value announced in master state and used for Best Master +; Clock selection +ptpengine:priority1 = 128 + +; Priority 2 value announced in master state and used for Best Master +; Clock selection +ptpengine:priority2 = 128 + +; Specify unicast destination for unicast master mode (in unicast +; slave mode overrides delay request destination) +ptpengine:unicast_address = + +; Send explicit IGMP joins between servo resets +ptpengine:igmp_refresh = Y + +; Multicast time to live for multicast PTP packets (ignored and set to +; 1 for peer to peer messages) +ptpengine:multicast_ttl = 64 + +; DiffServ CodepPoint for packet prioritisation (decimal). When set to +; zero, this option is not used. +; 46 = Expedited Forwarding (0x2e) +ptpengine:ip_dscp = 0 + +; Enable outlier filter for the Delay Response component in slave state +ptpengine:delay_outlier_filter_enable = N + +; Delay Response outlier filter action. If set to 'filter', outliers +; are replaced with moving average +; Options: discard filter +ptpengine:delay_outlier_filter_action = filter + +; Number of samples in the Delay Response outlier filter buffer +ptpengine:delay_outlier_filter_capacity = 20 + +; Delay Response outlier filter threshold: multiplier for the Peirce's +; maximum standard deviation. When set below 1.0, filter is tighter, +; when set above 1.0, filter is looser than standard Peirce's test. +ptpengine:delay_outlier_filter_threshold = 1.000000 + +; Delay Response outlier weight: if an outlier is detected, this value +; determines the amount of its deviation from mean that is used to +; build the standard deviation statistics and influence further +; outlier detection. +; When set to 1.0, the outlier is used as is. +; +ptpengine:delay_outlier_weight = 1.000000 + +; Enable outlier filter for the Sync component in slave state +ptpengine:sync_outlier_filter_enable = N + +; Sync outlier filter action. If set to 'filter', outliers are +; replaced with moving average +; Options: discard filter +ptpengine:sync_outlier_filter_action = filter + +; Number of samples in the Sync outlier filter buffer +ptpengine:sync_outlier_filter_capacity = 20 + +; Sync outlier filter threshold: multiplier for the Peirce's maximum +; standard deviation. When set below 1.0, filter is tighter, when set +; above 1.0, filter is looser than standard Peirce's test. +ptpengine:sync_outlier_filter_threshold = 1.000000 + +; Sync outlier weight: if an outlier is detected, this value +; determines the amount of its deviation from mean that is used to +; build the standard deviation statistics and influence further +; outlier detection. When set to 1.0, the outlier is used as is. +ptpengine:sync_outlier_weight = 1.000000 + +; Delay between moving to slave state and enabling clock updates +; expressed as number of statistics update periods (see +; global:statistics_update_interval). This allows one-way delay to +; stabilise before starting clock updates. Activated when going into +; slave state and during GM failover in slave state. +; 0 - not used. +ptpengine:calibration_delay = 0 + +; Enable panic mode: when offset from master is above 1 second, stop +; updating the clock for a period of time and then step the clock if +; offset remains above 1 second. +ptpengine:panic_mode = N + +; Duration of the panic mode period (no clock updates) when offset +; above 1 second detected +ptpengine:panic_mode_duration = 2 + +; Use JobID (PID) for UUID +ptpengine:pid_as_clock_idendity = N + +; Fail over to NTP when PTP time sync not available - requires +; ntpengine:enabled but does not require the rest of NTP configuration +; - will warn instead of failing over if cannot control ntpd. +ptpengine:ntp_failover = N + +; NTP failover timeout in seconds: time between PTP slave going into +; LISTENING state, and failing over to NTP. 0 = fail over immediately. +ptpengine:ntp_failover_timeout = 60 + +; Prefer NTP time synchronisation when not controlling the clock (all +; states, including slave when clock:no_adjust set) +ptpengine:prefer_ntp = N + +; When entering panic mode, fail over to NTP (after the NTP failover +; timeout period) - requires ntpengine:enabled but does not require +; the rest of NTP configuration - will warn instead of failing over if +; it cannot control ntpd. +ptpengine:panic_mode_ntp = N + +; Do not adjust the clock +clock:no_adjust = N + +; Do not reset the clock - only slew +clock:no_reset = N + +; Observed drift handling method between servo restarts: +; reset: set to zero (not recommended) +; preserve: use kernel value, +; file: load and save to drift file on startup/shutdown, use kernel +; value inbetween. +; To specify drift file, use the clock:drift_file setting. +; Options: reset preserve file +clock:drift_handling = preserve + +; Specify drift file +clock:drift_file = /etc/ptpd2_kernelclock.drift + +; Maximum absolute frequency shift which can be applied to the clock servo +; when slewing the clock. Expressed in parts per million (1 ppm = shift of +; 1 us per second. Values above 512 will use the tick duration correction +; to allow even faster slewing. Default maximum is 512 without using tick. +clock:max_offset_ppm = 500 + +; One-way delay filter stiffness +servo:delayfilter_stiffness = 6 + +; Clock servo PI controller proportional component gain (kP) +servo:kp = 0.1 + +; Clock servo PI controller integral component gain (kI) +servo:ki = 0.001 + +; Maximum accepted delayMS value in nanoseconds (Sync). +; 0 = not checked. +servo:max_delay = 0 + +; Enable clock synchronisation servo stability detection +; (based on standard deviation of the observed drift value) +; - drift will be saved to drift file / cached when considered stable, +; also clock stability status will be logged +; +servo:stability_detection = N + +; Specify the observed drift standard deviation threshold in parts per billion +; (ppb) - if stanard deviation is within the threshold, servo is considered +; stable. +servo:stability_threshold = 5.000000 + +; Specify for how many statistics update intervals the observed drift standard +; deviation has to stay within threshold to be considered stable +; +servo:stability_period = 3 + +; Specify after how many minutes without stabilisation servo is considered +; unstable. Assists with logging servo stability information and +; allows to preserve observed drift if servo cannot stabilise. +; +servo:stability_timeout = 10 + +; Do not update one-way delay if slave to master delay (from Delay Response) +; is greater than this value (nanoseconds). 0 = not used. +servo:max_delay = 0 + +; Do not reset the clock if offset from master is greater +; than this value (nanoseconds). 0 = not used. +servo:max_offset = 0 + +; Send log messages to syslog. Disabling this +; sends all messages to stdout (or speficied log file) +global:use_syslog = N + +; Lock file location +global:lock_file = + +; Use mode specific and interface specific lock files (overrides +; global:lock_file) +global:auto_lockfile = N + +; Lock file directory: used with automatic mode-specific lock files, +; also used when no lock file is specified. When lock file +; is specified, it's expected to be an absolute path. +global:lock_directory = /var/run + +; Skip lock file checking and locking +global:ignore_lock = N + +; File used to record data about sync packets. Setting this enables recording. +global:quality_file = + +; Maximum sync packet record file size (in kB) - file will be +; truncated if size exceeds the limit. +; 0 - no limit. +global:quality_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate. +global:quality_file_max_files = 0 + +; Truncate the sync packet record file every time it is (re) opened - +; on startup and SIGHUP +global:quality_file_truncate = N + +; File used to log ptpd2 status information +global:status_file = /var/run/ptpd2.status.log + +; Enable / disable writing status information to file +global:log_status = Y + +; Status file update interval in seconds +; +global:status_update_interval = 1 + +; Specify log file path (event log). Setting this enables logging to file. +global:log_file = /var/run/ptpd2.event.log + +; Maximum log file size (in kB) - log file will be truncated if size +; exceeds the limit. +; 0 - no limit. +global:log_file_max_size = 0 + +; Enable log rotation of the sync packet record file up to n files. +; 0 - do not rotate +global:log_file_max_files = 0 + +; Truncate the log file every time it is (re) opened - on startup and SIGHUP +global:log_file_truncate = N + +; Specify log level (only messages of the specified priority or higer +; will be logged). +; The minimal level is LOG_ERR. LOG_ALL enables debug output if compiled with +; RUNTIME_DEBUG +; Options: LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_ALL +global:log_level = LOG_ALL + +; Specify statistics log file path. Setting this enables logging of +; statistics but can be overriden with global:log_statistics +global:statistics_file = /var/run/ptpd2.stats.log + +; Log timing statistics every n seconds for Sync and Delay Response +; messages (0 - log all) +global:statistics_log_interval = 0 + +; Maximum statistics log file size (in kB) - log file will be +; truncated if size exceeds the limit. +; 0 - no limit. +global:statistics_file_max_size = 0 + +; Enable log rotation of the statistics file up to n files. 0 - do not rotate +; +global:statistics_file_max_files = 0 + +; Truncate the statistics file every time it is (re) opened - on +; startup and SIGHUP +global:statistics_file_truncate = N + +; Dump the contents of every PTP packet +global:dump_packets = N + +; Run in foreground with statistics and all messages logged to stdout. +; Overrides log file and statistics file settings and disables syslog. +; +global:verbose_foreground = N + +; Run in foreground +global:foreground = N + +; Log timing statistics for every PTP packet received +global:log_statistics = Y + +; Linux only: bind ptpd2 process to a selected CPU core number. +; 0 = first CPU core, etc. -1 = do not bind to a single core. +global:cpuaffinity_cpucore = -1 + +; Clock synchronisation statistics update interval in seconds +; +global:statistics_update_interval = 5 + +; Enable NTPd integration +ntpengine:enabled = N + +; Enable control over local NTPd daemon +ntpengine:control_enabled = N + +; NTP control check interval in seconds +; +ntpengine:check_interval = 15 + +; NTP key number - must be configured as a trusted control key in ntp.conf, +; and must be non-zero for the ntpengine:control_enabled setting to take effect. +; +ntpengine:key_id = 0 + +; NTP key (plain text, max. 20 characters) - must match the key +; configured in ntpd's keys file, and must be non-zero for the +; ntpengine:control_enabled setting to take effect. +ntpengine:key = + +; ========= newline required in the end ========== + diff --git a/src/ptpd/test/testing.org b/src/ptpd/test/testing.org new file mode 100644 index 0000000..f5fc928 --- /dev/null +++ b/src/ptpd/test/testing.org @@ -0,0 +1,22 @@ +Unified Test Plan for PTPd Versions + +NOTE: This file is in Emacs org mode, do not adjust the markup unless +absolutely necessary. + +* Client + +The client needs to be tested in the following matrix. For each test +there is a relevant config file listed. + +| | E2E | P2P | +| Socket | client-e2e-socket.conf | | +| PCAP | client-e2e-pcap.conf | | + +Note that for each configuration file you MUST update the +ptpengine:interface line at the beginning of the file. No other +changes should be necessary. + +* Server + +The server is tested with a series of its own clients. Not optimal + diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPArith_UT/SCORE_PTPArith_UT.cpp b/src/ptpd/tests/SCORE_UT/SCORE_PTPArith_UT/SCORE_PTPArith_UT.cpp new file mode 100644 index 0000000..e374933 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPArith_UT/SCORE_PTPArith_UT.cpp @@ -0,0 +1,363 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include "inc/score_ptp_arith.h" +#include "score_ptp_arith_mock.h" + +namespace testing { +namespace ptp_daemon_arith_ut { + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_SameSign_NanoSecBelowLimit_Success){ + score_ptp_arith mArith; + uint64_t timeValue = 10*SCORE_PTP_NANOSEC; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + Score_PtpConvertToTimeStamp(timeValue, 0u, &timeStampIn1); + Score_PtpConvertToTimeStamp(timeValue, 0u, &timeStampIn2); + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ(timeStampIn2.sign, timeStampIn1.sign); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn1.ts.nanoseconds+timeStampIn1.ts.nanoseconds)); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn1)+mArith.getTimeStampSecond(&timeStampIn2) + ); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_SameSign_NanoSecExceededLimit_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_POSITIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = 2; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ(timeStampIn2.sign, timeStampIn1.sign); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); + EXPECT_EQ(resultOut.ts.seconds, 1u); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn1.ts.nanoseconds+timeStampIn2.ts.nanoseconds-SCORE_PTP_NANOSEC)); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn1)+mArith.getTimeStampSecond(&timeStampIn2)+1u + ); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_SumIsZero_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 1u; + timeStampIn2.ts.seconds = 1u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ(resultOut.ts.nanoseconds, 0u); + EXPECT_EQ(mArith.getTimeStampSecond(&resultOut), 0u); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_DiffSign_SameSec_DifferentNanoSec_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 1u; + timeStampIn2.ts.seconds = 1u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ(mArith.getTimeStampSecond(&resultOut), 0u); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn2.ts.nanoseconds-timeStampIn1.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_DiffSign_FirstOpIsGreater_WithGreaterNanoSec_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn1.ts.seconds = 1u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn1)-mArith.getTimeStampSecond(&timeStampIn2) + ); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn1.ts.nanoseconds-timeStampIn2.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_DiffSign_FirstOpIsGreater_WithSmallerNanoSec_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 1u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn1)-mArith.getTimeStampSecond(&timeStampIn2)-1u + ); + EXPECT_EQ(resultOut.ts.nanoseconds, ((timeStampIn1.ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn2.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); +} + + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_DiffSign_SecondOpIsGreater_WithGreaterNanoSec_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 1u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn2)-mArith.getTimeStampSecond(&timeStampIn1) + ); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn2.ts.nanoseconds-timeStampIn1.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeSumAPI_DifferentSign_SecondOpIsGreater_WithSmallerNanoSec_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 1u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeSum(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn2)-mArith.getTimeStampSecond(&timeStampIn1)-1u + ); + EXPECT_EQ(resultOut.ts.nanoseconds, ((timeStampIn2.ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn1.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeDiffAPI_TimeDiffIsZero_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_POSITIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeDiff(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ(resultOut.ts.nanoseconds, 0u); + EXPECT_EQ(mArith.getTimeStampSecond(&resultOut), 0u); + EXPECT_EQ(resultOut.sign, SCORE_PTP_POSITIVE); +} + + +TEST(Score_PtpArithmetic, PtpCalculateTimeDiffAPI_SecondsValueIsEqual_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_POSITIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeDiff(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn2.ts.nanoseconds - timeStampIn1.ts.nanoseconds)); + EXPECT_EQ(mArith.getTimeStampSecond(&resultOut), 0u); + EXPECT_EQ(resultOut.sign, SCORE_PTP_NEGATIVE); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeDiffAPI_FirstOperandIsGreater_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn1.ts.seconds = 1u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeDiff(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn1)-mArith.getTimeStampSecond(&timeStampIn2) + ); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn1.ts.nanoseconds - timeStampIn2.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeDiffAPI_FirstOperandIsGreater_WithSmallerNs_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 1u; + timeStampIn2.ts.seconds = 0u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeDiff(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn1)-mArith.getTimeStampSecond(&timeStampIn2)-1u + ); + EXPECT_EQ(resultOut.ts.nanoseconds, ((timeStampIn1.ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn2.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn1.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeDiffAPI_SecondOperandIsGreater_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 1u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeDiff(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn2)-mArith.getTimeStampSecond(&timeStampIn1) + ); + EXPECT_EQ(resultOut.ts.nanoseconds, (timeStampIn2.ts.nanoseconds - timeStampIn1.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); +} + +TEST(Score_PtpArithmetic, PtpCalculateTimeDiffAPI_SecondOperandIsGreater_WithSmallerNs_Success){ + score_ptp_arith mArith; + Score_PtpSignedTimeStampType timeStampIn1; + Score_PtpSignedTimeStampType timeStampIn2; + Score_PtpSignedTimeStampType resultOut; + + timeStampIn1.sign = SCORE_PTP_POSITIVE; + timeStampIn2.sign = SCORE_PTP_NEGATIVE; + timeStampIn1.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT; + timeStampIn2.ts.nanoseconds = SCORE_PTP_NS_UPPERLIMIT-100; + timeStampIn1.ts.seconds = 0u; + timeStampIn2.ts.seconds = 1u; + timeStampIn1.ts.secondsHi = 0u; + timeStampIn2.ts.secondsHi = 0u; + + Score_PtpCalculateTimeDiff(&timeStampIn1, &timeStampIn2, &resultOut); + EXPECT_EQ( + mArith.getTimeStampSecond(&resultOut), + mArith.getTimeStampSecond(&timeStampIn2)-mArith.getTimeStampSecond(&timeStampIn1)-1u + ); + EXPECT_EQ(resultOut.ts.nanoseconds, ((timeStampIn2.ts.nanoseconds + SCORE_PTP_NANOSEC) - timeStampIn1.ts.nanoseconds)); + EXPECT_EQ(resultOut.sign, timeStampIn2.sign); +} + + +TEST(Score_PtpArithmetic, PtpConvertToTimeStampAPI_ReturnsPositiveValue_Success){ + score_ptp_arith mArith; + uint64_t timeValue = 10; + uint8_t factor = 10; + Score_PtpSignedTimeStampType timeStampResult; + Score_PtpConvertToTimeStamp(timeValue, factor, &timeStampResult); + EXPECT_EQ(timeStampResult.sign, SCORE_PTP_POSITIVE); +} + +TEST(Score_PtpArithmetic, PtpInterpolateTimeAPI_ReturnPositiveValue_Success){ + score_ptp_arith mArith; + TSync_VirtualLocalTimeType vltCurrentIn {SCORE_PTP_NANOSEC, 0u}; + TSync_VirtualLocalTimeType vltPreviousIn {SCORE_PTP_NS_UPPERLIMIT, 0u}; + Score_PtpSignedTimeStampType resultCurrentTimeStampOut; + resultCurrentTimeStampOut.sign = SCORE_PTP_POSITIVE; + Score_PtpSignedTimeStampType globalTimeStampPreviousIn; + uint64_t timeValue = SCORE_PTP_NANOSEC; + Score_PtpConvertToTimeStamp(timeValue, 0u, &globalTimeStampPreviousIn); + Score_PtpInterpolateTime(&vltCurrentIn, &vltPreviousIn, &globalTimeStampPreviousIn, &resultCurrentTimeStampOut); + EXPECT_EQ(resultCurrentTimeStampOut.sign, SCORE_PTP_POSITIVE); + EXPECT_EQ(resultCurrentTimeStampOut.ts.nanoseconds, 1u); + EXPECT_EQ(resultCurrentTimeStampOut.ts.seconds, 1u); + EXPECT_EQ(resultCurrentTimeStampOut.ts.secondsHi, 0u); +} + +} // namespace testing +} // namespace ptp_daemon_arith_ut \ No newline at end of file diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPArith_UT/SCORE_PTPArith_UT.rst b/src/ptpd/tests/SCORE_UT/SCORE_PTPArith_UT/SCORE_PTPArith_UT.rst new file mode 100644 index 0000000..a6b938a --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPArith_UT/SCORE_PTPArith_UT.rst @@ -0,0 +1,25 @@ +SCORE_PTPArith Unit-Tests +====================== + +Test Specification +------------------ + +.. doxygennamespace:: testing::ptp_daemon_arith_ut + :project: score-ptp-daemon + :content-only: + +Test Results +------------ + +.. test-file:: Score_PTPArith test-results + :file: _test_reports/ut_reports/SCORE_PTPArith_UT_report.xml + :id: UT_TESTFILE_SCORE_PTPARITH + :tags: UT + :auto_suites: + :auto_cases: + +Test Execution Log +------------------ + +.. literalinclude:: ../../../_test_reports/ut_reports/SCORE_PTPArith_UT_report.xml + :language: none diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.cpp b/src/ptpd/tests/SCORE_UT/SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.cpp new file mode 100644 index 0000000..8772a78 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.cpp @@ -0,0 +1,371 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include + +#include "inc/score_ptp_common.h" +#include "score_ptp_common_mock.h" +#include "ptp_datatypes.h" + +using ::testing::_; +using ::testing::Return; + +// Define global function +PtpCommonMock ptpCommonMock; + +Score_PtpConsumerDataType consumerData; +Score_PtpProviderDataType providerData; +class ScorePtpCommonTest : public ::testing::Test { +protected: + PtpCommonMock ptp_common; + const uint16_t testDomainNumber = 1; + // Because all the function that interact with TSync_TimeBaseHandleType is mocked + // For simplicity Timebase_handler_ is just an address to a int variable + const int Timebase_handler_ = 0; + + void SetUp() override { + ON_CALL(ptpCommonMock, TSync_Open()).WillByDefault(Return(E_OK)); + ON_CALL(ptpCommonMock, TSync_OpenTimebase(_)).WillByDefault(Return((void*)(&Timebase_handler_))); + ON_CALL(ptpCommonMock, TSync_RegisterTransmitGlobalTimeCallback(_, _)).WillByDefault(Return(E_OK)); + ON_CALL(ptpCommonMock, Score_PtpTransmitGlobalTimeCallback(_)).WillByDefault(Return(E_OK)); + ON_CALL(ptpCommonMock, TSync_GetTimebaseConfiguration(_, _, _)).WillByDefault(Return(E_OK)); + ptpConfig = {}; + } + + void TearDown() override { + testing::Mock::VerifyAndClearExpectations(&ptpCommonMock); + } +}; + +namespace testing { +namespace ptp_daemon_common_ut { + +TEST_F(ScorePtpCommonTest, Score_PtpInitialize_WithTsyncOpenFailed_Failed) { + // Arrange + EXPECT_CALL(ptpCommonMock, TSync_Open()).WillOnce(Return(E_NOT_OK)); + double pdelay = 0; + Score_PtpBooleanType isProvider = false; + // Act + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + // Assert + EXPECT_EQ(result, SCORE_PTP_E_NOT_OK); + EXPECT_EQ(ptpConfig.timebaseHandle, nullptr); +} + +TEST_F(ScorePtpCommonTest, Score_PtpInitialize_WithTsyncOpenTimebaseFailed_Failed) { + // Arrange + EXPECT_CALL(ptpCommonMock, TSync_OpenTimebase((TSync_SynchronizedTimeBaseType)(testDomainNumber))) + .WillOnce(Return(nullptr)); + double pdelay = 0; + Score_PtpBooleanType isProvider = false; + // Act + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + // Assert + EXPECT_EQ(result, SCORE_PTP_E_NOT_OK); + EXPECT_EQ(ptpConfig.timebaseHandle, nullptr); +} + +TEST_F(ScorePtpCommonTest, Score_PtpInitialize_WithRegisterTransmitGlobalTimeCallbackFailed_Failed) { + // Arrange + EXPECT_CALL(ptpCommonMock, TSync_RegisterTransmitGlobalTimeCallback(_, _)).WillOnce(Return(E_NOT_OK)); + double pdelay = 0; + Score_PtpBooleanType isProvider = true; + // Act + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + // Assert + EXPECT_EQ(result, SCORE_PTP_E_NOT_OK); +} + +TEST_F(ScorePtpCommonTest, Score_PtpInitialize_WithPtpConfigureFailed_Failed) { + // Arrange + EXPECT_CALL(ptpCommonMock, TSync_GetTimebaseConfiguration(_, _, _)).WillOnce(Return(E_NOT_OK)); + double pdelay = 0; + Score_PtpBooleanType isProvider = true; + // Act + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + // Assert + EXPECT_EQ(result, SCORE_PTP_E_NOT_OK); +} + +TEST_F(ScorePtpCommonTest, Score_PtpInitialize_WithisProviderTrue_Success) { + // Arrange + // Set up ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS; + double pdelay = 0; + Score_PtpBooleanType isProvider = true; + // Act + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + // Assert + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, Score_PtpInitialize_WithisProviderFalse_Success) { + // Arrange + // Set up ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS; + double pdelay = 0; + Score_PtpBooleanType isProvider = false; + // Act + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + // Assert + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, Score_PtpDeInitialize_Success) { + // Arrange + // Initialize first + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS; + double pdelay = 0; + Score_PtpBooleanType isProvider = false; + Score_PtpReturnType result = Score_PtpInitialize(pdelay, testDomainNumber, isProvider); + EXPECT_EQ(result, SCORE_PTP_E_OK); + EXPECT_NE(ptpConfig.timebaseHandle, nullptr); + + // Act + EXPECT_CALL(ptpCommonMock, TSync_CloseTimebase(ptpConfig.timebaseHandle)).Times(1); + EXPECT_CALL(ptpCommonMock, TSync_Close()).Times(1); + Score_PtpDeinitialize(); + // Assert + EXPECT_EQ(ptpConfig.timebaseHandle, nullptr); + EXPECT_EQ(consumerData.syncState, SCORE_PTP_STATE_INVALID); +} + +TEST_F(ScorePtpCommonTest, DISABLED_Score_PtpConfigure_With_TSYNC_MC_IEEE8021AS_AUTOSAR_AsProvider_Success) { + // Arrange + Score_PtpBooleanType isProvider = true; + const int64_t syncPeriodMs = 5; + // Setup ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.message_length = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.domain_number = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.correction_field = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.source_port_identity = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.sequence_id = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.syncPeriodMs = syncPeriodMs; + // Act + Score_PtpReturnType result = Score_PtpConfigure(isProvider); + // Assert + uint8_t expect_followupLength = SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR + sizeof(Score_PtpSubtlvTimeSecuredType) + + sizeof(Score_PtpSubtlvStatusType) + sizeof(Score_PtpSubtlvUserdataType); + uint8_t expect_configSubTlvLength = + sizeof(Score_PtpSubtlvTimeSecuredType) + sizeof(Score_PtpSubtlvStatusType) + sizeof(Score_PtpSubtlvUserdataType); + EXPECT_EQ(ptpConfig.configGlobals.followupLength, expect_followupLength); + EXPECT_EQ(ptpConfig.configGlobals.configSubTlvLength, expect_configSubTlvLength); + // Sync period remains the same as it was initialized - if it were 0, it would have been initialized to 1 + EXPECT_EQ(ptpConfig.timebaseConfig.syncPeriodMs, syncPeriodMs); + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, DISABLED_Score_PtpConfigure_With_TSYNC_MC_IEEE8021AS_AUTOSAR_AsConsumer_Success) { + // Arrange + Score_PtpBooleanType isProvider = false; + // Setup ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.message_length = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.domain_number = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.correction_field = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.source_port_identity = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.sequence_id = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.syncPeriodMs = 0; + + // Act + Score_PtpReturnType result = Score_PtpConfigure(isProvider); + // Assert + uint8_t expect_followupLength = SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR + sizeof(Score_PtpSubtlvTimeSecuredType) + + sizeof(Score_PtpSubtlvStatusType) + sizeof(Score_PtpSubtlvUserdataType); + uint8_t expect_configSubTlvLength = + sizeof(Score_PtpSubtlvTimeSecuredType) + sizeof(Score_PtpSubtlvStatusType) + sizeof(Score_PtpSubtlvUserdataType); + EXPECT_EQ(ptpConfig.configGlobals.followupLength, expect_followupLength); + EXPECT_EQ(ptpConfig.configGlobals.configSubTlvLength, expect_configSubTlvLength); + EXPECT_EQ(ptpConfig.timebaseConfig.syncPeriodMs, SCORE_PTP_DEFAULT_SYNC_PERIOD); + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, DISABLED_Score_PtpConfigure_UpdateNoCrcFlag_Success) { + // Arrange + Score_PtpBooleanType isProvider = false; + // Setup ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.message_length = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.domain_number = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.correction_field = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.source_port_identity = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.sequence_id = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp = TSYNC_CRC_NOT_SUPPORTED; + + // Act + Score_PtpReturnType result = Score_PtpConfigure(isProvider); + // Assert + uint8_t expect_followupLength = SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR + sizeof(Score_PtpSubtlvTimeSecuredType) + + sizeof(Score_PtpSubtlvStatusType) + sizeof(Score_PtpSubtlvUserdataType); + uint8_t expect_configSubTlvLength = + sizeof(Score_PtpSubtlvTimeSecuredType) + sizeof(Score_PtpSubtlvStatusType) + sizeof(Score_PtpSubtlvUserdataType); + EXPECT_EQ(ptpConfig.configGlobals.followupLength, expect_followupLength); + EXPECT_EQ(ptpConfig.configGlobals.configSubTlvLength, expect_configSubTlvLength); + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, DISABLED_Score_PtpConfigure_With_NoSubTLVSupported_AsProvider_Success) { + // Arrange + Score_PtpBooleanType isProvider = true; + // Setup ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.message_length = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.domain_number = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.correction_field = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.source_port_identity = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.sequence_id = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp = TSYNC_CRC_NOT_SUPPORTED; + + // Act + Score_PtpReturnType result = Score_PtpConfigure(isProvider); + // Assert + EXPECT_EQ(ptpConfig.configGlobals.followupLength, SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR); + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, DISABLED_Score_PtpConfigure_With_NoSubTLVSupported_AsConsumer_Success) { + // Arrange + Score_PtpBooleanType isProvider = false; + // Setup ptpConfig + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_NOT_VALIDATED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.message_length = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.domain_number = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.correction_field = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.source_port_identity = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.sequence_id = TSYNC_CRC_NOT_SUPPORTED; + ptpConfig.timebaseConfig.crcFlags.precise_origin_timestamp = TSYNC_CRC_NOT_SUPPORTED; + + // Act + Score_PtpReturnType result = Score_PtpConfigure(isProvider); + // Assert + EXPECT_EQ(ptpConfig.configGlobals.followupLength, SCORE_PTP_FUP_PACKET_LENGTH_FIXED_AUTOSAR); + EXPECT_EQ(result, SCORE_PTP_E_OK); +} + +TEST_F(ScorePtpCommonTest, Score_PtpExtractFupHeaderInfo_WithNullfollowupHeaderInfo_Failed) { + // Arrange + MsgHeaderTag* followupHeaderInfo = nullptr; + Score_PtpBooleanType isProvider = true; + // Act + Score_PtpExtractFupHeaderInfo(followupHeaderInfo, isProvider); + // Assert +} + +TEST_F(ScorePtpCommonTest, Score_PtpExtractFupHeaderInfo_AsProvider_Success) { + // Arrange + struct MsgHeaderTag followupHeaderInfo; + followupHeaderInfo.transportSpecific = 0u; + followupHeaderInfo.messageType = 1u; + followupHeaderInfo.reserved0 = 1u; + followupHeaderInfo.versionPTP = 1u; + followupHeaderInfo.messageLength = 32u; + followupHeaderInfo.domainNumber = static_cast(testDomainNumber); + followupHeaderInfo.flagField0 = 0u; + followupHeaderInfo.flagField1 = 1u; + followupHeaderInfo.correctionField.msb = 1u; + followupHeaderInfo.correctionField.lsb = 0u; + followupHeaderInfo.sourcePortIdentity.portNumber = 32u; + std::fill(followupHeaderInfo.sourcePortIdentity.clockIdentity, + followupHeaderInfo.sourcePortIdentity.clockIdentity + CLOCK_IDENTITY_LENGTH, 0); + followupHeaderInfo.sequenceId = 1u; + followupHeaderInfo.controlField = 0u; + followupHeaderInfo.logMessageInterval = 1u; + + Score_PtpBooleanType isProvider = true; + // Act + Score_PtpExtractFupHeaderInfo(&followupHeaderInfo, isProvider); + // Assert + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.transportSpecific, followupHeaderInfo.transportSpecific); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.messageType, followupHeaderInfo.messageType); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.reserved0, followupHeaderInfo.reserved0); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.versionPtp, followupHeaderInfo.versionPTP); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.messageLength, followupHeaderInfo.messageLength); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.domainNumber, followupHeaderInfo.domainNumber); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.flagField0, followupHeaderInfo.flagField0); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.flagField1, followupHeaderInfo.flagField1); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.sequenceId, followupHeaderInfo.sequenceId); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.controlField, followupHeaderInfo.controlField); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.logMessageInterval, followupHeaderInfo.logMessageInterval); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.controlField, + ((followupHeaderInfo.correctionField.msb << 32) | (followupHeaderInfo.correctionField.lsb))); + EXPECT_EQ(providerData.followUpMsg.followupHeaderInfo.sourcePortIdentity.portNumber, + followupHeaderInfo.sourcePortIdentity.portNumber); + for (int i = 0; i < CLOCK_IDENTITY_LENGTH; i++) { + EXPECT_EQ(followupHeaderInfo.sourcePortIdentity.clockIdentity[i], + providerData.followUpMsg.followupHeaderInfo.sourcePortIdentity.clockIdentity[i]); + } +} + +TEST_F(ScorePtpCommonTest, Score_PtpExtractFupHeaderInfo_AsConsumer_Success) { + // Arrange + struct MsgHeaderTag followupHeaderInfo; + followupHeaderInfo.transportSpecific = 0u; + followupHeaderInfo.messageType = 1u; + followupHeaderInfo.reserved0 = 1u; + followupHeaderInfo.versionPTP = 1u; + followupHeaderInfo.messageLength = 32u; + followupHeaderInfo.domainNumber = static_cast(testDomainNumber); + followupHeaderInfo.flagField0 = 0u; + followupHeaderInfo.flagField1 = 1u; + followupHeaderInfo.correctionField.msb = 1u; + followupHeaderInfo.correctionField.lsb = 0u; + followupHeaderInfo.sourcePortIdentity.portNumber = 32u; + std::fill(followupHeaderInfo.sourcePortIdentity.clockIdentity, + followupHeaderInfo.sourcePortIdentity.clockIdentity + CLOCK_IDENTITY_LENGTH, 0); + followupHeaderInfo.sequenceId = 1u; + followupHeaderInfo.controlField = 0u; + followupHeaderInfo.logMessageInterval = 1u; + + Score_PtpBooleanType isProvider = false; + // Act + Score_PtpExtractFupHeaderInfo(&followupHeaderInfo, isProvider); + // Assert + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.transportSpecific, followupHeaderInfo.transportSpecific); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.messageType, followupHeaderInfo.messageType); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.reserved0, followupHeaderInfo.reserved0); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.versionPtp, followupHeaderInfo.versionPTP); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.messageLength, followupHeaderInfo.messageLength); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.domainNumber, followupHeaderInfo.domainNumber); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.flagField0, followupHeaderInfo.flagField0); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.flagField1, followupHeaderInfo.flagField1); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.sequenceId, followupHeaderInfo.sequenceId); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.controlField, followupHeaderInfo.controlField); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.logMessageInterval, followupHeaderInfo.logMessageInterval); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.controlField, + ((followupHeaderInfo.correctionField.msb << 32) | (followupHeaderInfo.correctionField.lsb))); + EXPECT_EQ(consumerData.followUpMsg.followupHeaderInfo.sourcePortIdentity.portNumber, + followupHeaderInfo.sourcePortIdentity.portNumber); + for (int i = 0; i < CLOCK_IDENTITY_LENGTH; i++) { + EXPECT_EQ(followupHeaderInfo.sourcePortIdentity.clockIdentity[i], + consumerData.followUpMsg.followupHeaderInfo.sourcePortIdentity.clockIdentity[i]); + } +} + +} // namespace ptp_daemon_common_ut +} // namespace testing diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.rst b/src/ptpd/tests/SCORE_UT/SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.rst new file mode 100644 index 0000000..e9eda12 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.rst @@ -0,0 +1,25 @@ +SCORE_PTPCommon Unit-Tests +======================= + +Test Specification +------------------ + +.. doxygennamespace:: testing::ptp_daemon_common_ut + :project: score-ptp-daemon + :content-only: + +Test Results +------------ + +.. test-file:: SCORE_PTPCommon test-results + :file: _test_reports/ut_reports/SCORE_PTPCommon_UT_report.xml + :id: UT_TESTFILE_SCORE_PTPCOMMON + :tags: UT + :auto_suites: + :auto_cases: + +Test Execution Log +------------------ + +.. literalinclude:: ../../../_test_reports/ut_reports/SCORE_PTPCommon_UT_report.xml + :language: none \ No newline at end of file diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.cpp b/src/ptpd/tests/SCORE_UT/SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.cpp new file mode 100644 index 0000000..92663b2 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.cpp @@ -0,0 +1,362 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include "inc/score_ptp_consumer.h" +#include "score_ptp_consumer_mock.h" + +extern Score_PtpConsumerDataType consumerData; + +Score_PtpConfigType ptpConfig = {}; + +// Create a global instance of the PtpConsumerMock class +PtpConsumerMock ptpConsumerMock; + +// Define a mock implementation of TSync_GetCurrentVirtualLocalTime +TSync_ReturnType TSync_GetCurrentVirtualLocalTime(TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr) { + // Forward the call to the mock instance + return ptpConsumerMock.TSync_GetCurrentVirtualLocalTime(timeBaseHandle, localTimePtr); +} + +// Define a mock implementation of TSync_BusSetGlobalTime +TSync_ReturnType TSync_BusSetGlobalTime(TSync_TimeBaseHandleType timeBaseHandle, + const TSync_TimeStampType* globalTimePtr, + const TSync_UserDataType* userDataPtr, + const TSync_MeasurementType* measureDataPtr, + const TSync_VirtualLocalTimeType* localTimePtr) { + // Forward the call to the mock instance + return ptpConsumerMock.TSync_BusSetGlobalTime(timeBaseHandle, globalTimePtr, userDataPtr, measureDataPtr, localTimePtr); +} + +// Define a mock implementation of Score_PtpReadSubTlvFromBuffer +Score_PtpBooleanType Score_PtpReadSubTlvFromBuffer(const uint8_t *autosarTlvBufferIn) { + // Forward the call to the mock instance + return ptpConsumerMock.Score_PtpReadSubTlvFromBuffer(autosarTlvBufferIn); +} + +class ScorePtpConsumerTest : public ::testing::Test { +protected: + PtpConsumerMock ptpConsumer; + + void SetUp() override { + // Reset consumerData before each test + memset(&consumerData, 0, sizeof(Score_PtpConsumerDataType)); + } + + void TearDown() override { + // Verify and clear mock expectations + testing::Mock::VerifyAndClearExpectations(&ptpConsumerMock); + } +}; + +namespace testing { +namespace ptp_daemon_consumer_ut { + +TEST_F(ScorePtpConsumerTest, Score_PtpReadFupInfoFromBuffer_SyncStateReceived_SubTlvReadSuccess) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_SYNC_RCVD; + TSync_TimeStampType correctionField = {TIMEBASE_STATUS_BIT_GLOBAL_SYNC, SCORE_PTP_NANOSEC, 1u, 0u}; + Score_PtpTimeStampType originTime = {1u, 0u, SCORE_PTP_NANOSEC}; + uint8_t fupInfoIn = 0x01; + + uint8_t corrTimeIn[sizeof(TSync_TimeStampType)]; + uint8_t originTimeIn[sizeof(Score_PtpTimeStampType)]; + + // Copy the structs into byte arrays + memcpy(corrTimeIn, &correctionField, sizeof(TSync_TimeStampType)); + memcpy(originTimeIn, &originTime, sizeof(Score_PtpTimeStampType)); + + EXPECT_CALL(ptpConsumerMock, Score_PtpReadSubTlvFromBuffer(_)) + .WillOnce(Return(SCORE_PTP_TRUE)); + + // Act + Score_PtpReadFupInfoFromBuffer(originTimeIn, corrTimeIn, &fupInfoIn); + + // Assert + EXPECT_EQ(consumerData.correctionField.ts.secondsHi, correctionField.secondsHi); + EXPECT_EQ(consumerData.correctionField.ts.seconds, correctionField.seconds); + EXPECT_EQ(consumerData.correctionField.ts.nanoseconds, correctionField.nanoseconds); + EXPECT_EQ(consumerData.correctionField.sign, SCORE_PTP_POSITIVE); + + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, originTime.secondsHi); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.seconds, originTime.seconds); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds, originTime.nanoseconds); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.sign, SCORE_PTP_POSITIVE); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadFupInfoFromBuffer_SyncStateReceived_SubTlvReadFailure) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_SYNC_RCVD; + TSync_TimeStampType correctionField = {TIMEBASE_STATUS_BIT_GLOBAL_SYNC, SCORE_PTP_NANOSEC, 1u, 0u}; + Score_PtpTimeStampType originTime = {1u, 0u, SCORE_PTP_NANOSEC}; + uint8_t fupInfoIn = 0x01; + + uint8_t corrTimeIn[sizeof(TSync_TimeStampType)]; + uint8_t originTimeIn[sizeof(Score_PtpTimeStampType)]; + + // Copy the structs into byte arrays + memcpy(corrTimeIn, &correctionField, sizeof(TSync_TimeStampType)); + memcpy(originTimeIn, &originTime, sizeof(Score_PtpTimeStampType)); + + EXPECT_CALL(ptpConsumerMock, Score_PtpReadSubTlvFromBuffer(_)) + .WillOnce(Return(SCORE_PTP_FALSE)); + + // Act + Score_PtpReadFupInfoFromBuffer(originTimeIn, corrTimeIn, &fupInfoIn); + + // Assert + EXPECT_EQ(consumerData.correctionField.ts.secondsHi, correctionField.secondsHi); + EXPECT_EQ(consumerData.correctionField.ts.seconds, correctionField.seconds); + EXPECT_EQ(consumerData.correctionField.ts.nanoseconds, correctionField.nanoseconds); + EXPECT_EQ(consumerData.correctionField.sign, SCORE_PTP_POSITIVE); + + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, originTime.secondsHi); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.seconds, originTime.seconds); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds, originTime.nanoseconds); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.sign, SCORE_PTP_POSITIVE); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadFupInfoFromBuffer_NullInputs_Failure) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_SYNC_RCVD; + TSync_TimeStampType correctionField = {TIMEBASE_STATUS_BIT_GLOBAL_SYNC, SCORE_PTP_NANOSEC, 1u, 0u}; + + // NULL for fupInfoIn and originTimeIn + uint8_t* fupInfoIn = NULL; + uint8_t* originTimeIn = NULL; + uint8_t corrTimeIn[sizeof(TSync_TimeStampType)]; + + // Copy the struct into the byte array + memcpy(corrTimeIn, &correctionField, sizeof(TSync_TimeStampType)); + + // Act + Score_PtpReadFupInfoFromBuffer(originTimeIn, corrTimeIn, fupInfoIn); + + // Assert + EXPECT_EQ(consumerData.correctionField.ts.secondsHi, 0); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, 0); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadFupInfoFromBuffer_SyncStateFupReceived_Failure) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_FUP_RCVD; + TSync_TimeStampType correctionField = {TIMEBASE_STATUS_BIT_GLOBAL_SYNC, SCORE_PTP_NANOSEC, 1u, 0u}; + Score_PtpTimeStampType originTime = {1u, 0u, SCORE_PTP_NANOSEC}; + uint8_t fupInfoIn = 0x01; + + uint8_t corrTimeIn[sizeof(TSync_TimeStampType)]; + uint8_t originTimeIn[sizeof(Score_PtpTimeStampType)]; + + // Copy the structs into byte arrays + memcpy(corrTimeIn, &correctionField, sizeof(TSync_TimeStampType)); + memcpy(originTimeIn, &originTime, sizeof(Score_PtpTimeStampType)); + + // Act + Score_PtpReadFupInfoFromBuffer(originTimeIn, corrTimeIn, &fupInfoIn); + + // Assert + EXPECT_EQ(consumerData.correctionField.ts.secondsHi, 0); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, 0); + + EXPECT_NE(consumerData.correctionField.sign, SCORE_PTP_POSITIVE); + EXPECT_NE(consumerData.followUpMsg.preciseOriginTimestamp.sign, SCORE_PTP_POSITIVE); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadFupInfoFromBuffer_InvalidSyncState_Failure) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_INVALID; + TSync_TimeStampType correctionField = {TIMEBASE_STATUS_BIT_GLOBAL_SYNC, SCORE_PTP_NANOSEC, 1u, 0u}; + Score_PtpTimeStampType originTime = {1u, 0u, SCORE_PTP_NANOSEC}; + + uint8_t corrTimeIn[sizeof(TSync_TimeStampType)]; + uint8_t originTimeIn[sizeof(Score_PtpTimeStampType)]; + uint8_t fupInfoIn = 0x01; + + // Copy the structs into byte arrays + memcpy(corrTimeIn, &correctionField, sizeof(TSync_TimeStampType)); + memcpy(originTimeIn, &originTime, sizeof(Score_PtpTimeStampType)); + + // Act + Score_PtpReadFupInfoFromBuffer(originTimeIn, corrTimeIn, &fupInfoIn); + + // Assert + EXPECT_EQ(consumerData.correctionField.ts.secondsHi, 0); + EXPECT_EQ(consumerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, 0); + + EXPECT_NE(consumerData.correctionField.sign, SCORE_PTP_POSITIVE); + EXPECT_NE(consumerData.followUpMsg.preciseOriginTimestamp.sign, SCORE_PTP_POSITIVE); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadIngressVlt_T1_Success) { + // Arrange + TSync_VirtualLocalTimeType virtualLocalTime = {1u, 1u}; + + // Set up the mock behavior + EXPECT_CALL(ptpConsumerMock, TSync_GetCurrentVirtualLocalTime(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(virtualLocalTime), Return(E_OK))); + + // Act + Score_PtpReadIngressVlt(SCORE_PTP_INGRESS_T1); + + // Assert + EXPECT_EQ(consumerData.t1Vlt.nanosecondsHi, virtualLocalTime.nanosecondsHi); + EXPECT_EQ(consumerData.t1Vlt.nanosecondsLo, virtualLocalTime.nanosecondsLo); + EXPECT_EQ(consumerData.syncState, SCORE_PTP_STATE_SYNC_RCVD); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadIngressVlt_T2_Success) { + // Arrange + TSync_VirtualLocalTimeType virtualLocalTime = {1u, 1u}; + + // Set up the mock behavior + EXPECT_CALL(ptpConsumerMock, TSync_GetCurrentVirtualLocalTime(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(virtualLocalTime), Return(E_OK))); + + // Act + Score_PtpReadIngressVlt(SCORE_PTP_INGRESS_T2); + + // Assert + EXPECT_EQ(consumerData.t2Vlt.nanosecondsHi, virtualLocalTime.nanosecondsHi); + EXPECT_EQ(consumerData.t2Vlt.nanosecondsLo, virtualLocalTime.nanosecondsLo); + EXPECT_EQ(consumerData.syncState, SCORE_PTP_STATE_FUP_RCVD); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadIngressVlt_Failure) { + // Arrange + // Set up the mock behavior to return E_NOT_OK + EXPECT_CALL(ptpConsumerMock, TSync_GetCurrentVirtualLocalTime) + .WillOnce(Return(E_NOT_OK)); + + // Act + Score_PtpReadIngressVlt(SCORE_PTP_INGRESS_T1); + + // Assert + EXPECT_EQ(consumerData.syncState, SCORE_PTP_STATE_INVALID); + EXPECT_EQ(consumerData.t1Vlt.nanosecondsHi, 0); + EXPECT_EQ(consumerData.t1Vlt.nanosecondsLo, 0); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpReadIngressVlt_InvalidIngress) { + + // Arrange & Act + Score_PtpReadIngressVlt(SCORE_PTP_INGRESS_UNDEFINED); + + // Assert + EXPECT_EQ(consumerData.t1Vlt.nanosecondsHi, 0); + EXPECT_EQ(consumerData.t1Vlt.nanosecondsLo, 0); + EXPECT_EQ(consumerData.t2Vlt.nanosecondsHi, 0); + EXPECT_EQ(consumerData.t2Vlt.nanosecondsLo, 0); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpBusSetGlobalTime_FUPReceived_Success) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_FUP_RCVD; + + Score_PtpSignedTimeStampType syncTimeStamp = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType timeStampDiff = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + + uint64_t syncRxTimeStamp = 0u; + uint64_t fupRxTimeStamp = 0u; + + TSync_TimeStampType globalTime = {0u, 0u, 0u, 0u}; + Score_PtpSignedTimeStampType finalGlobalTime = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + + TSync_UserDataType userdata = {0u, 0u, 0u, 0u}; + + TSync_MeasurementType measureData = {0u}; + TSync_VirtualLocalTimeType localTime = {0u, 0u}; + + EXPECT_CALL(ptpConsumerMock, TSync_BusSetGlobalTime(_, _, _, _, _)) + .WillOnce(Return(E_OK)); + + // Act + Score_PtpBusSetGlobalTime(); + + // Assert + EXPECT_EQ(syncTimeStamp.ts.nanoseconds, timeStampDiff.ts.nanoseconds); + EXPECT_EQ(syncTimeStamp.ts.seconds, timeStampDiff.ts.seconds); + EXPECT_EQ(syncTimeStamp.ts.secondsHi, timeStampDiff.ts.secondsHi); + + // Expected syncRxTimeStamp: Combine t1Vlt.nanosecondsHi and t1Vlt.nanosecondsLo + uint64_t expectedSyncRxTimeStamp = ((uint64_t)consumerData.t1Vlt.nanosecondsHi << 32u) | + consumerData.t1Vlt.nanosecondsLo; + + // Expected fupRxTimeStamp: Combine t2Vlt.nanosecondsHi and t2Vlt.nanosecondsLo + uint64_t expectedFupRxTimeStamp = ((uint64_t)consumerData.t2Vlt.nanosecondsHi << 32u) | + consumerData.t2Vlt.nanosecondsLo; + + EXPECT_EQ(syncRxTimeStamp, expectedSyncRxTimeStamp); + EXPECT_EQ(fupRxTimeStamp, expectedFupRxTimeStamp); + + EXPECT_EQ(measureData.pathDelay, ptpConfig.pdelay.ts.nanoseconds); + + EXPECT_EQ(localTime.nanosecondsLo, consumerData.t2Vlt.nanosecondsLo); + EXPECT_EQ(localTime.nanosecondsHi, consumerData.t2Vlt.nanosecondsHi); + + EXPECT_EQ(globalTime.nanoseconds, finalGlobalTime.ts.nanoseconds); + EXPECT_EQ(globalTime.seconds, finalGlobalTime.ts.seconds); + EXPECT_EQ(globalTime.secondsHi, finalGlobalTime.ts.secondsHi); + + EXPECT_EQ(userdata.userDataLength, consumerData.followUpMsg.subTlv.userdata.userDataLength); + EXPECT_EQ(userdata.userByte0, consumerData.followUpMsg.subTlv.userdata.userByte0); + EXPECT_EQ(userdata.userByte1, consumerData.followUpMsg.subTlv.userdata.userByte1); + EXPECT_EQ(userdata.userByte2, consumerData.followUpMsg.subTlv.userdata.userByte2); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpBusSetGlobalTime_FUPReceived_Failure) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_FUP_RCVD; + + Score_PtpSignedTimeStampType syncTimeStamp = {{0u, 0u, 0u, 0u}, SCORE_PTP_POSITIVE}; + Score_PtpSignedTimeStampType timeStampDiff = {{1u, 1u, 1u, 1u}, SCORE_PTP_POSITIVE}; + + TSync_TimeStampType globalTime = {0u, 0u, 0u, 0u}; + Score_PtpSignedTimeStampType finalGlobalTime = {{1u, 1u, 1u, 1u}, SCORE_PTP_POSITIVE}; + + TSync_MeasurementType measureData = {1u}; + + EXPECT_CALL(ptpConsumerMock, TSync_BusSetGlobalTime(_, _, _, _, _)) + .WillOnce(Return(E_NOT_OK)); + + // Act + Score_PtpBusSetGlobalTime(); + + // Assert + EXPECT_NE(syncTimeStamp.ts.nanoseconds, timeStampDiff.ts.nanoseconds); + EXPECT_NE(syncTimeStamp.ts.seconds, timeStampDiff.ts.seconds); + EXPECT_NE(syncTimeStamp.ts.secondsHi, timeStampDiff.ts.secondsHi); + + EXPECT_NE(measureData.pathDelay, ptpConfig.pdelay.ts.nanoseconds); + + EXPECT_NE(globalTime.nanoseconds, finalGlobalTime.ts.nanoseconds); + EXPECT_NE(globalTime.seconds, finalGlobalTime.ts.seconds); + EXPECT_NE(globalTime.secondsHi, finalGlobalTime.ts.secondsHi); +} + +TEST_F(ScorePtpConsumerTest, Score_PtpBusSetGlobalTime_SyncStateNotFUP_NoGlobalTimeSet) { + // Arrange + consumerData.syncState = SCORE_PTP_STATE_SYNC_RCVD; + + TSync_TimeStampType globalTime = {0u, 0u, 0u, 0u}; + TSync_UserDataType userdata = {0u, 0u, 0u, 0u}; + TSync_MeasurementType measureData = {0u}; + + // Set up the mock behavior to return E_NOT_OK + EXPECT_CALL(ptpConsumerMock, TSync_GetCurrentVirtualLocalTime) + .WillOnce(Return(E_NOT_OK)); + + // Act + Score_PtpBusSetGlobalTime(); + + // Assert + EXPECT_EQ(consumerData.syncState, SCORE_PTP_STATE_INVALID); + + EXPECT_EQ(globalTime.nanoseconds, 0); + EXPECT_EQ(userdata.userDataLength, 0); + EXPECT_EQ(measureData.pathDelay, 0); +} + +} // namespace ptp_daemon_consumer_ut +} // namespace testing diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.rst b/src/ptpd/tests/SCORE_UT/SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.rst new file mode 100644 index 0000000..ae2b0b9 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.rst @@ -0,0 +1,25 @@ +SCORE_PTPConsumer Unit-Tests +========================= + +Test Specification +------------------ + +.. doxygennamespace:: testing::ptp_daemon_consumer_ut + :project: score-ptp-daemon + :content-only: + +Test Results +------------ + +.. test-file:: SCORE_PTPConsumer test-results + :file: _test_reports/ut_reports/SCORE_PTPConsumer_UT_report.xml + :id: UT_TESTFILE_SCORE_PTPCONSUMER + :tags: UT + :auto_suites: + :auto_cases: + +Test Execution Log +------------------ + +.. literalinclude:: ../../../_test_reports/ut_reports/SCORE_PTPConsumer_UT_report.xml + :language: none diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.cpp b/src/ptpd/tests/SCORE_UT/SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.cpp new file mode 100644 index 0000000..ea634d7 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.cpp @@ -0,0 +1,320 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score_ptp_provider_mock.h" + +#include +#include + +using ::testing::_; +using ::testing::Return; + +extern Score_PtpProviderDataType providerData; + +Score_PtpConfigType ptpConfig = {}; + +// Create a global instance of the PtpProviderMock class +PtpProviderMock ptpProviderMock; + +// Define a mock implementation of TSync_GetCurrentVirtualLocalTime +TSync_ReturnType TSync_GetCurrentVirtualLocalTime(TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr) { + // Forward the call to the mock instance + return ptpProviderMock.TSync_GetCurrentVirtualLocalTime(timeBaseHandle, localTimePtr); +} + +// Define a mock implementation of TSync_BusGetGlobalTime +TSync_ReturnType TSync_BusGetGlobalTime(TSync_TimeBaseHandleType timeBaseHandle, + TSync_TimeStampType* globalTimePtr, + TSync_UserDataType* userDataPtr, + TSync_MeasurementType* measureDataPtr, + TSync_VirtualLocalTimeType* localTimePtr) { + // Forward the call to the mock instance + return ptpProviderMock.TSync_BusGetGlobalTime(timeBaseHandle, globalTimePtr, userDataPtr, measureDataPtr, localTimePtr); +} + +class ScorePtpProviderTest : public ::testing::Test { +protected: + PtpProviderMock ptpProvider; + void SetUp() override { + + // Reset providerData before each test + memset(&providerData, 0, sizeof(Score_PtpProviderDataType)); + } + + void TearDown() override { + testing::Mock::VerifyAndClearExpectations(&ptpProviderMock); + } +}; + +namespace testing { +namespace ptp_daemon_provider_ut { + + +TEST_F(ScorePtpProviderTest, Score_PtpReadEgressVlt_TSync_GetCurrentVirtualLocalTime_Failure) { + // Arrange + // Set up the mock behavior + EXPECT_CALL(ptpProviderMock, TSync_GetCurrentVirtualLocalTime) + .WillOnce(Return(E_NOT_OK)); + + // Set some default values + providerData.t4Vlt.nanosecondsHi = 0xDEAD; + providerData.t4Vlt.nanosecondsLo = 0xDEAD; + + // Act + Score_PtpReadEgressVlt(SCORE_PTP_EGRESS_T4); + + // Assert + EXPECT_EQ(providerData.t4Vlt.nanosecondsHi, 0xDEAD); + EXPECT_EQ(providerData.t4Vlt.nanosecondsLo, 0xDEAD); +} + +TEST_F(ScorePtpProviderTest, Score_PtpReadEgressVlt_TSync_GetCurrentVirtualLocalTime_T4_Success) { + + // Arrange + TSync_VirtualLocalTimeType virtualLocalTime = {0xC001, 0xCAFE}; + + EXPECT_CALL(ptpProviderMock, TSync_GetCurrentVirtualLocalTime(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(virtualLocalTime), Return(E_OK))); + + providerData.t4Vlt.nanosecondsHi = 0xDEAD; + providerData.t4Vlt.nanosecondsLo = 0xDEAD; + + // Act + Score_PtpReadEgressVlt(SCORE_PTP_EGRESS_T4); + + // Assert + EXPECT_EQ(providerData.t4Vlt.nanosecondsLo, 0xC001); + EXPECT_EQ(providerData.t4Vlt.nanosecondsHi, 0xCAFE); +} + +TEST_F(ScorePtpProviderTest, Score_PtpReadEgressVlt_TSync_GetCurrentVirtualLocalTime_T0_Success) { + + // Arrange + TSync_VirtualLocalTimeType virtualLocalTime = {0xC001, 0xCAFE}; + + // Arrange + EXPECT_CALL(ptpProviderMock, TSync_GetCurrentVirtualLocalTime(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(virtualLocalTime), Return(E_OK))); + + providerData.t4Vlt.nanosecondsHi = 0xDEAD; + providerData.t4Vlt.nanosecondsLo = 0xDEAD; + + // Act + Score_PtpReadEgressVlt(SCORE_PTP_EGRESS_T0); + + // Assert + EXPECT_EQ(providerData.t4Vlt.nanosecondsLo, 0xDEAD); + EXPECT_EQ(providerData.t4Vlt.nanosecondsHi, 0xDEAD); +} + +TEST_F(ScorePtpProviderTest, Score_PtpBusGetGlobalTime_TSync_BusGetGlobalTime_Failure) { + // Arrange + // Set up the mock behavior + EXPECT_CALL(ptpProviderMock, TSync_BusGetGlobalTime(_,_,_,_,_)) + .WillOnce(Return(E_NOT_OK)); + + // Set some default values + providerData.t0Vlt.nanosecondsHi = 0xDEAD; + providerData.t0Vlt.nanosecondsLo = 0xDEAD; + + // Act + Score_PtpBusGetGlobalTime(); + + // Assert + EXPECT_EQ(providerData.t0Vlt.nanosecondsHi, 0xDEAD); + EXPECT_EQ(providerData.t0Vlt.nanosecondsLo, 0xDEAD); +} + +TEST_F(ScorePtpProviderTest, Score_PtpBusGetGlobalTime_TSync_BusGetGlobalTime_Success) { + + // Arrange + EXPECT_CALL(ptpProviderMock, TSync_BusGetGlobalTime(_,_,_,_,_)) + .WillOnce(Return(E_OK)); + + providerData.t0Vlt.nanosecondsHi = 0xDEAD; + providerData.t0Vlt.nanosecondsLo = 0xDEAD; + + // Act + Score_PtpBusGetGlobalTime(); + + // Assert + EXPECT_EQ(providerData.t0Vlt.nanosecondsLo, 0); + EXPECT_EQ(providerData.t0Vlt.nanosecondsHi, 0); + + // Arrange + TSync_TimeStampType globalTime = {0x00 ,0xA, 0xB, 0xC}; + TSync_UserDataType userdata = {0x3, 0xB0, 0xB1, 0xB2}; + TSync_VirtualLocalTimeType virtualLocalTime = {0xC001, 0xCAFE}; + + EXPECT_CALL(ptpProviderMock, TSync_BusGetGlobalTime(_,_,_,_,_)) + .WillOnce(DoAll(SetArgPointee<1>(globalTime), + SetArgPointee<2>(userdata), + SetArgPointee<4>(virtualLocalTime), Return(E_OK))); + + // Act + Score_PtpBusGetGlobalTime(); + + // Assert + EXPECT_EQ(providerData.t0Vlt.nanosecondsLo, 0xC001); + EXPECT_EQ(providerData.t0Vlt.nanosecondsHi, 0xCAFE); + EXPECT_EQ(providerData.t0GlobalTime.nanoseconds, 0xA); + EXPECT_EQ(providerData.t0GlobalTime.seconds, 0xB); + EXPECT_EQ(providerData.t0GlobalTime.secondsHi, 0xC); + EXPECT_EQ(providerData.followUpMsg.subTlv.userdata.userDataLength, 0x3); + EXPECT_EQ(providerData.followUpMsg.subTlv.userdata.userByte0, 0xB0); + EXPECT_EQ(providerData.followUpMsg.subTlv.userdata.userByte1, 0xB1); + EXPECT_EQ(providerData.followUpMsg.subTlv.userdata.userByte2, 0xB2); + + //Arrange + EXPECT_CALL(ptpProviderMock, TSync_BusGetGlobalTime(_,_,_,_,_)) + .WillOnce(Return(E_OK)); + + // Act + Score_PtpBusGetGlobalTime(); +} + +TEST_F(ScorePtpProviderTest, Score_PtpProviderComputeOriginTime_Success) { + // Arrange + /* Set Current Virtual Local Time to 4000000000ns which means 4 seconds */ + TSync_VirtualLocalTimeType virtualLocalTime = {0xEE6B2800, 0x00}; + + // Current Virtual Local Time + EXPECT_CALL(ptpProviderMock, TSync_GetCurrentVirtualLocalTime(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(virtualLocalTime), Return(E_OK))); + + // Previous Global Time + providerData.t0Vlt.nanosecondsHi = 0x00; + providerData.t0Vlt.nanosecondsLo = 0x00; + + providerData.t0GlobalTime.secondsHi = 0x00; + providerData.t0GlobalTime.seconds = 0x00; + providerData.t0GlobalTime.nanoseconds = 0x00; + + // Current GlobalTime = PreviousGlobalTime + (CurrentVLT - PreviousVLT); + TSync_TimeStampType globalTimeExpected; + globalTimeExpected.seconds = 4; + globalTimeExpected.secondsHi = 0; + globalTimeExpected.nanoseconds = 0; + + // Act + Score_PtpProviderComputeOriginTime(); + + // Assert + EXPECT_EQ(globalTimeExpected.seconds, providerData.followUpMsg.preciseOriginTimestamp.ts.seconds); + EXPECT_EQ(globalTimeExpected.secondsHi, providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi); + EXPECT_EQ(globalTimeExpected.nanoseconds, providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds); +} + +TEST_F(ScorePtpProviderTest, Score_PtpWriteOriginTimeToBuffer_Success) { + + // Arrange + uint8_t originTimeBuffer[12]; + + providerData.followUpMsg.preciseOriginTimestamp.ts.seconds = 0xA; + providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi = 0xB; + providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds = 0xC; + + //Act + Score_PtpWriteOriginTimeToBuffer(&originTimeBuffer[0],sizeof(Score_PtpTimeStampType)); + + Score_PtpTimeStampType originTimeStamp; + memcpy((void*)(&originTimeStamp), (void*)(&originTimeBuffer[0]), sizeof(Score_PtpTimeStampType)); + + // Assert + EXPECT_EQ(providerData.followUpMsg.preciseOriginTimestamp.ts.seconds, 0xA); + EXPECT_EQ(providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, 0xB); + EXPECT_EQ(providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds, 0xC); +} + +TEST_F(ScorePtpProviderTest, Score_PtpWriteOriginTimeToBuffer_OnInvalidBufferSize_Failure) { + + // Arrange + uint8_t originTimeBuffer[12]; + + providerData.followUpMsg.preciseOriginTimestamp.ts.seconds = 0xA; + providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi = 0xB; + providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds = 0xC; + + //Act + Score_PtpWriteOriginTimeToBuffer(&originTimeBuffer[0],1); + + // Assert + EXPECT_EQ(providerData.followUpMsg.preciseOriginTimestamp.ts.seconds, 0xA); + EXPECT_EQ(providerData.followUpMsg.preciseOriginTimestamp.ts.secondsHi, 0xB); + EXPECT_EQ(providerData.followUpMsg.preciseOriginTimestamp.ts.nanoseconds, 0xC); +} + + +TEST_F(ScorePtpProviderTest, Score_PtpTransmitGlobalTimeCallback_Domain_Id_Invalid) { + // Arrange + // Set the configured domain id + ptpConfig.timebaseId = 1; + TSync_SynchronizedTimeBaseType invalidDomainId = 12; + + //Act + TSync_ReturnType returnValue = Score_PtpTransmitGlobalTimeCallback(invalidDomainId); + + // Assert + EXPECT_EQ(returnValue, E_NOT_OK); +} + +TEST_F(ScorePtpProviderTest, Score_PtpTransmitGlobalTimeCallback_Success) { + // Arrange + // Set the configured domain id + const TSync_SynchronizedTimeBaseType validDomainId = 1; + ptpConfig.timebaseId = validDomainId; + + // Act + TSync_ReturnType returnValue = Score_PtpTransmitGlobalTimeCallback(validDomainId); + + // Assert + EXPECT_EQ(returnValue, E_OK); + EXPECT_EQ(providerData.immediateTimeTransmission, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpProviderTest, Score_PtpTransmitGlobalTimeCallback_OnCallFromDifferentThread_FlagChangeIsVisible) { + //Arrange + providerData.immediateTimeTransmission = SCORE_PTP_FALSE; + const TSync_SynchronizedTimeBaseType validDomainId = 1; + ptpConfig.timebaseId = validDomainId; + EXPECT_EQ(providerData.immediateTimeTransmission, SCORE_PTP_FALSE); + + std::thread trigger_thread([&]() { + EXPECT_EQ(providerData.immediateTimeTransmission, SCORE_PTP_FALSE); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + Score_PtpTransmitGlobalTimeCallback(validDomainId); + }); + + trigger_thread.join(); + + // Assert + EXPECT_EQ(providerData.immediateTimeTransmission, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpProviderTest, Score_PtpIsImmediateTimeTriggerTrue_Success) { + //Arrange + providerData.immediateTimeTransmission = SCORE_PTP_TRUE; + + // Act + bool triggerValue = Score_PtpIsImmediateTimeTriggerTrue(); + + // Assert + EXPECT_EQ(triggerValue, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpProviderTest, Score_PtpResetImmediateTimeTrigger_Success) { + // Arrange + providerData.immediateTimeTransmission = SCORE_PTP_TRUE; + + // Act + Score_PtpResetImmediateTimeTrigger(); + + // Assert + EXPECT_EQ(providerData.immediateTimeTransmission, SCORE_PTP_FALSE); +} + +} // namespace ptp_daemon_provider_ut +} // namespace testing diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.rst b/src/ptpd/tests/SCORE_UT/SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.rst new file mode 100644 index 0000000..39a2b92 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.rst @@ -0,0 +1,25 @@ +SCORE_PTPProvider Unit-Tests +========================= + +Test Specification +------------------ + +.. doxygennamespace:: testing::ptp_daemon_provider_ut + :project: score-ptp-daemon + :content-only: + +Test Results +------------ + +.. test-file:: SCORE_PTPProvider_ test-results + :file: _test_reports/ut_reports/SCORE_PTPProvider_UT_report.xml + :id: UT_TESTFILE_SCORE_PTPPROVIDER + :tags: UT + :auto_suites: + :auto_cases: + +Test Execution Log +------------------ + +.. literalinclude:: ../../../_test_reports/ut_reports/SCORE_PTPProvider_UT_report.xml + :language: none diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.cpp b/src/ptpd/tests/SCORE_UT/SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.cpp new file mode 100644 index 0000000..fb0b2e8 --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.cpp @@ -0,0 +1,686 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include "inc/score_ptp_subtlv.h" +#include "score_ptp_subtlv_mock.h" + +using ::testing::_; +using ::testing::Return; + +PtpSubTlvMock ptpSubTlvMock; + +Score_PtpConsumerDataType consumerData; +Score_PtpProviderDataType providerData; +Score_PtpConfigType ptpConfig = {}; + +uint8 Crc_CalculateCRC8H2F(const uint8* Crc_DataPtr, uint32 Crc_Length, uint8 Crc_StartValue8, + boolean Crc_IsFirstCall) { + return ptpSubTlvMock.Crc_CalculateCRC8H2F(Crc_DataPtr, Crc_Length, Crc_StartValue8, Crc_IsFirstCall); +} + +class ScorePtpSubtlvTest : public ::testing::Test { +protected: + PtpSubTlvMock ptp_sub_tlv; + const uint8 crc_sample_data = 25; + + void SetUp() override { + ON_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, _, _)).WillByDefault(Return(0)); + // Setup provider + providerData.followUpMsg.subTlv.userdata.userDataLength = 3u; + providerData.followUpMsg.subTlv.userdata.userByte0 = 1u; + providerData.followUpMsg.subTlv.userdata.userByte1 = 2u; + providerData.followUpMsg.subTlv.userdata.userByte2 = 3u; + ptpConfig = {}; + } + + void TearDown() override { + testing::Mock::VerifyAndClearExpectations(&ptpSubTlvMock); + } +}; + +namespace testing { +namespace ptp_daemon_subtlv_ut { + +TEST_F(ScorePtpSubtlvTest, Score_PtpWriteAutosarTlvToBuffer_Not_TSYNC_MC_IEEE8021AS_AUTOSAR_Succeed) { + // Arrange + uint8_t output[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + // Act + Score_PtpWriteAutosarTlvToBuffer(&output[0]); + // Assert +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpWriteAutosarTlvToBuffer_No_TSYNC_SUBTLV_SUPPORTED_Succeed) { + // Arrange + uint8_t output[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + // Act + Score_PtpWriteAutosarTlvToBuffer(&output[0]); + // Assert +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpWriteAutosarTlvToBuffer_InputIsNull_Failed) { + // Arrange + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + // Act + Score_PtpWriteAutosarTlvToBuffer(nullptr); + // Assert +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpWriteAutosarTlvToBuffer_NoCRCFlagAreSet_Succeed) { + // Arrange + uint8_t output[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + // Set bit + ptpConfig.configGlobals.crcTimeFlags = 0; + // Check bit + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + // Act + Score_PtpWriteAutosarTlvToBuffer(&output[0]); + // Assert + Score_PtpAutosarTlvType autosarTlv; + memcpy((void*)(&autosarTlv), (void*)(&output), sizeof(Score_PtpAutosarTlvType)); + EXPECT_GE(autosarTlv.tlvType, 0u); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_TIMESECURED); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTimeFlags, ptpConfig.configGlobals.crcTimeFlags); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataLen, providerData.followUpMsg.subTlv.userdata.userDataLength); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte0, providerData.followUpMsg.subTlv.userdata.userByte0); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte1, providerData.followUpMsg.subTlv.userdata.userByte1); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte2, providerData.followUpMsg.subTlv.userdata.userByte2); + EXPECT_EQ(autosarTlv.subTLVUserdata.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC); + EXPECT_EQ(autosarTlv.subTLVUserdata.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_USERDATA); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpWriteAutosarTlvToBuffer_AllCRCFlagAreSet_Succeed) { + // Arrange + uint8_t output[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + // Set bit + ptpConfig.configGlobals.crcTimeFlags = 0; + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK); + // Check bit + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + // Act + Score_PtpWriteAutosarTlvToBuffer(&output[0]); + // Assert + Score_PtpAutosarTlvType autosarTlv; + memcpy((void*)(&autosarTlv), (void*)(&output), sizeof(Score_PtpAutosarTlvType)); + EXPECT_GE(autosarTlv.tlvType, 0u); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_TIMESECURED); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTime0, crc_sample_data); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTime1, crc_sample_data); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTimeFlags, ptpConfig.configGlobals.crcTimeFlags); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataLen, providerData.followUpMsg.subTlv.userdata.userDataLength); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte0, providerData.followUpMsg.subTlv.userdata.userByte0); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte1, providerData.followUpMsg.subTlv.userdata.userByte1); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte2, providerData.followUpMsg.subTlv.userdata.userByte2); + EXPECT_EQ(autosarTlv.subTLVUserdata.crcUserdata, crc_sample_data); + EXPECT_EQ(autosarTlv.subTLVUserdata.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC); + EXPECT_EQ(autosarTlv.subTLVUserdata.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_USERDATA); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpWriteAutosarTlvToBuffer_WithoutCRCSupported_Succeed) { + // Arrange + uint8_t output[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).Times(0); + // Act + Score_PtpWriteAutosarTlvToBuffer(&output[0]); + // Assert + Score_PtpSubtlvUserdataType userData; + uint8_t* userPtr = output + SCORE_PTP_FUP_AUTOSAR_TLV_INFO_LENGTH + SCORE_PTP_FUP_SUBTLV_STATUS_LENGTH; + memcpy((void*)(&userData), (void*)(userPtr), sizeof(Score_PtpSubtlvUserdataType)); + EXPECT_EQ(userData.userdataLen, providerData.followUpMsg.subTlv.userdata.userDataLength); + EXPECT_EQ(userData.userdataByte0, providerData.followUpMsg.subTlv.userdata.userByte0); + EXPECT_EQ(userData.userdataByte1, providerData.followUpMsg.subTlv.userdata.userByte1); + EXPECT_EQ(userData.userdataByte2, providerData.followUpMsg.subTlv.userdata.userByte2); + EXPECT_EQ(userData.crcUserdata, 0u); + EXPECT_EQ(userData.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC); + EXPECT_EQ(userData.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_USERDATA); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpWriteAutosarTlvToBuffer_With_numFupDataIdEntries_Succeed) { + // Arrange + uint8_t output[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.numFupDataIdEntries = 1u; + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + // Act + Score_PtpWriteAutosarTlvToBuffer(&output[0]); + // Assert + Score_PtpAutosarTlvType autosarTlv; + memcpy((void*)(&autosarTlv), (void*)(&output), sizeof(Score_PtpAutosarTlvType)); + EXPECT_GE(autosarTlv.tlvType, 0u); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_TIMESECURED); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTime0, crc_sample_data); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTime1, crc_sample_data); + EXPECT_EQ(autosarTlv.subTLVTimeSecured.crcTimeFlags, ptpConfig.configGlobals.crcTimeFlags); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataLen, providerData.followUpMsg.subTlv.userdata.userDataLength); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte0, providerData.followUpMsg.subTlv.userdata.userByte0); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte1, providerData.followUpMsg.subTlv.userdata.userByte1); + EXPECT_EQ(autosarTlv.subTLVUserdata.userdataByte2, providerData.followUpMsg.subTlv.userdata.userByte2); + EXPECT_EQ(autosarTlv.subTLVUserdata.crcUserdata, crc_sample_data); + EXPECT_EQ(autosarTlv.subTLVUserdata.subtlvType, SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC); + EXPECT_EQ(autosarTlv.subTLVUserdata.subtlvLength, SCORE_PTP_FUP_SUBTLV_LEN_FIELD_USERDATA); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTlvFromBuffer_NoValueSetUp_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTlvFromBuffer_InputIsNull_Succeed) { + // Arrange + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + auto res = Score_PtpReadSubTlvFromBuffer(nullptr); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTlvFromBuffer_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set bit + ptpConfig.configGlobals.crcTimeFlags = 0; + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK); + SCORE_PTP_SET_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK); + // Check bit + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK)); + EXPECT_TRUE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + autosarTlv.subTLVTimeSecured.crcTime0 = crc_sample_data; + autosarTlv.subTLVTimeSecured.crcTime1 = crc_sample_data; + autosarTlv.subTLVUserdata.crcUserdata = crc_sample_data; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTlvFromBuffer_WithNoCrcFlagSet_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set bit + ptpConfig.configGlobals.crcTimeFlags = 0; + // Check bit + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_MESSAGE_LENGTH_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_DOMAIN_NUMBER_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_CORRECTION_FIELD_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SOURCE_PORT_ID_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_SEQUENCE_ID_MASK)); + EXPECT_FALSE(SCORE_PTP_CHECK_CRC_FLAG(ptpConfig.configGlobals.crcTimeFlags, SCORE_PTP_CRC_TIMEFLAG_PRECISE_ORIGIN_TIME_MASK)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + autosarTlv.subTLVTimeSecured.crcTime0 = crc_sample_data; + autosarTlv.subTLVTimeSecured.crcTime1 = crc_sample_data; + autosarTlv.subTLVUserdata.crcUserdata = crc_sample_data; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTlvFromBuffer_WithZeroSubTlvConfigured_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.configGlobals.configSubTlvLength = 0u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_CRC_And_TSYNC_CRC_NOT_VALIDATED_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_NOT_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_TSYNC_CRC_IGNORED_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_IGNORED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_NOT_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITHOUT_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVUserdata_With_Invalid_SubtlvType_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_NOT_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITHOUT_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVUserdata_With_Invalid_CrcValidation_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = 0x10; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITHOUT_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_numFupDataIdEntries_GreaterThanZero_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.timebaseConfig.numFupDataIdEntries = 1u; + ptpConfig.configGlobals.configSubTlvLength = 1u; + consumerData.followUpMsg.followupHeaderInfo.sequenceId = 0x1; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITHOUT_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + autosarTlv.subTLVUserdata.crcUserdata = crc_sample_data; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_crcComputed_Eq_crcUserData_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + autosarTlv.subTLVUserdata.crcUserdata = 10; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)) + .WillOnce(Return(static_cast(10))); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVUserdata_With_InconsistenciesCRC_Failed) { + // Arrange + /**/ + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + autosarTlv.subTLVUserdata.crcUserdata = 123; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)) + .WillOnce(Return(static_cast(10))); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVTimeSecured_WithoutSupportFor_followUpTimeSubTLV_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.configGlobals.configSubTlvLength = 1u; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + // Set up input + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.subTLVStatus.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.subTLVUserdata.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITH_CRC; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVTimeSecured_CRCIsNotValid_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.timebaseConfig.crcValidation = TSYNC_CRC_NOT_VALIDATED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVTimeSecured_Without_SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_USERDATA_WITHOUT_CRC; + autosarTlv.tlvLength = 1; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVTimeSecured_SubTlvReceiveDoNotMatchConfiguredSubTlv_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.tlvLength = 0; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.configGlobals.configSubTlvLength = 123; + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVStatus_With_unsupported_SubTLV_ReturnZero_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + ptpConfig.timebaseConfig.crcSupport = TSYNC_CRC_SUPPORTED; + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.configGlobals.configSubTlvLength = 1u; + ptpConfig.configGlobals.followupLength = 8; + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_STATUS_WITH_CRC; + autosarTlv.tlvLength = 1u; + autosarTlv.subTLVTimeSecured.crcTime0 = crc_sample_data; + autosarTlv.subTLVTimeSecured.crcTime1 = crc_sample_data; + autosarTlv.subTLVUserdata.crcUserdata = crc_sample_data; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)).WillRepeatedly(Return(crc_sample_data)); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userDataLength, autosarTlv.subTLVUserdata.userdataLen); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte0, autosarTlv.subTLVUserdata.userdataByte0); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte1, autosarTlv.subTLVUserdata.userdataByte1); + EXPECT_EQ(consumerData.followUpMsg.subTlv.userdata.userByte2, autosarTlv.subTLVUserdata.userdataByte2); +} + +TEST_F(ScorePtpSubtlvTest, DISABLED_Score_PtpReadSubTLVTimeSecured_With_TimeSecuredWithInconsistenciesCRC_Failed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.tlvLength = 1; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.timebaseConfig.subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_SUPPORTED; + ptpConfig.configGlobals.configSubTlvLength = 1u; + EXPECT_CALL(ptpSubTlvMock, Crc_CalculateCRC8H2F(_, _, 0u, SCORE_PTP_TRUE)) + .WillRepeatedly(Return(static_cast(12))); + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_FALSE); +} + +TEST_F(ScorePtpSubtlvTest, Score_PtpReadSubTLVTimeSecured_Without_followUpTimeSubTLV_TSYNC_SUBTLV_SUPPORTED_Succeed) { + // Arrange + uint8_t input[SCORE_PTP_TOTAL_AUTOSAR_TLV_LENGTH]; + Score_PtpAutosarTlvType autosarTlv; + autosarTlv.subTLVTimeSecured.subtlvType = SCORE_PTP_FUP_SUBTLV_TYPE_TIMESECURED; + autosarTlv.tlvLength = 1; + memcpy((void*)(&input), (void*)(&autosarTlv), sizeof(Score_PtpAutosarTlvType)); + ptpConfig.timebaseConfig.messageCompliance = TSYNC_MC_IEEE8021AS_AUTOSAR; + ptpConfig.configGlobals.configSubTlvLength = 1u; + // Act + auto res = Score_PtpReadSubTlvFromBuffer(&input[0]); + // Assert + EXPECT_EQ(res, SCORE_PTP_TRUE); +} + +} // namespace ptp_daemon_subtlv_ut +} // namespace testing diff --git a/src/ptpd/tests/SCORE_UT/SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.rst b/src/ptpd/tests/SCORE_UT/SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.rst new file mode 100644 index 0000000..086ce9b --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.rst @@ -0,0 +1,25 @@ +SCORE_PTPSubtlv Unit-Tests +======================= + +Test Specification +------------------ + +.. doxygennamespace:: testing::ptp_daemon_subtlv_ut + :project: score-ptp-daemon + :content-only: + +Test Results +------------ + +.. test-file:: SCORE_PTPSubtlv test-results + :file: _test_reports/ut_reports/SCORE_PTPSubtlv_UT_report.xml + :id: UT_TESTFILE_SCORE_PTPSUBTLV + :tags: UT + :auto_suites: + :auto_cases: + +Test Execution Log +------------------ + +.. literalinclude:: ../../../_test_reports/ut_reports/SCORE_PTPSubtlv_UT_report.xml + :language: none \ No newline at end of file diff --git a/src/ptpd/tests/SCORE_UT/unit-tests.rst b/src/ptpd/tests/SCORE_UT/unit-tests.rst new file mode 100644 index 0000000..db7a08f --- /dev/null +++ b/src/ptpd/tests/SCORE_UT/unit-tests.rst @@ -0,0 +1,16 @@ +############# +SW Unit Tests +############# + +************************ +Unit-Test Specifications +************************ + +.. toctree:: + :maxdepth: 1 + + SCORE_PTPArith_UT/SCORE_PTPArith_UT.rst + SCORE_PTPCommon_UT/SCORE_PTPCommon_UT.rst + SCORE_PTPConsumer_UT/SCORE_PTPConsumer_UT.rst + SCORE_PTPSubtlv_UT/SCORE_PTPSubtlv_UT.rst + SCORE_PTPProvider_UT/SCORE_PTPProvider_UT.rst diff --git a/src/ptpd/tests/common/mocks/include/score_ptp_arith_mock.h b/src/ptpd/tests/common/mocks/include/score_ptp_arith_mock.h new file mode 100644 index 0000000..bca6f65 --- /dev/null +++ b/src/ptpd/tests/common/mocks/include/score_ptp_arith_mock.h @@ -0,0 +1,13 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "inc/score_ptp_arith.h" +#include + +class score_ptp_arith { +public: + uint64_t getTimeStampSecond(Score_PtpSignedTimeStampType* timeStampIn) { + return (uint64_t)((((uint64_t)(timeStampIn->ts.secondsHi)) << 32) | (timeStampIn->ts.seconds)); + } +}; \ No newline at end of file diff --git a/src/ptpd/tests/common/mocks/include/score_ptp_common_mock.h b/src/ptpd/tests/common/mocks/include/score_ptp_common_mock.h new file mode 100644 index 0000000..4328972 --- /dev/null +++ b/src/ptpd/tests/common/mocks/include/score_ptp_common_mock.h @@ -0,0 +1,62 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_COMMON_MOCK_H +#define SCORE_PTP_COMMON_MOCK_H + +#include + +#include "inc/score_ptp_common.h" + +#define CLOCK_IDENTITY_LENGTH 8 + +class PtpCommonMock { +public: + // Mock method for tsync_ptp_lib functions + MOCK_METHOD(TSync_ReturnType, TSync_Open, ()); + MOCK_METHOD(TSync_TimeBaseHandleType, TSync_OpenTimebase, (TSync_SynchronizedTimeBaseType)); + MOCK_METHOD(TSync_ReturnType, TSync_RegisterTransmitGlobalTimeCallback, + (TSync_TimeBaseHandleType timeBaseHandle, TSync_TransmitGlobalTimeCallback cb)); + MOCK_METHOD(void, TSync_Close, ()); + MOCK_METHOD(void, TSync_CloseTimebase, (TSync_TimeBaseHandleType timeBaseHandle)); + MOCK_METHOD(TSync_ReturnType, Score_PtpTransmitGlobalTimeCallback, (TSync_SynchronizedTimeBaseType domainId)); + MOCK_METHOD(TSync_ReturnType, TSync_GetTimebaseConfiguration, + (TSync_TimeBaseHandleType timeBaseHandle, TSync_Role role_requested, + TSync_TimeBaseConfiguration* config)); +}; + +extern PtpCommonMock ptpCommonMock; + +// Define stub functions +TSync_ReturnType TSync_Open(void) { + return ptpCommonMock.TSync_Open(); +} + +TSync_ReturnType TSync_RegisterTransmitGlobalTimeCallback(TSync_TimeBaseHandleType timeBaseHandle, + TSync_TransmitGlobalTimeCallback cb) { + return ptpCommonMock.TSync_RegisterTransmitGlobalTimeCallback(timeBaseHandle, cb); +} + +void TSync_Close(void) { + ptpCommonMock.TSync_Close(); +} + +void TSync_CloseTimebase(TSync_TimeBaseHandleType timeBaseHandle) { + return ptpCommonMock.TSync_CloseTimebase(timeBaseHandle); +} + +TSync_TimeBaseHandleType TSync_OpenTimebase(TSync_SynchronizedTimeBaseType timeBaseId) { + return ptpCommonMock.TSync_OpenTimebase(timeBaseId); +} + +TSync_ReturnType Score_PtpTransmitGlobalTimeCallback(TSync_SynchronizedTimeBaseType domainId) { + return ptpCommonMock.Score_PtpTransmitGlobalTimeCallback(domainId); +} + +TSync_ReturnType TSync_GetTimebaseConfiguration(TSync_TimeBaseHandleType timeBaseHandle, TSync_Role role_requested, + TSync_TimeBaseConfiguration* config) { + return ptpCommonMock.TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, config); +} + +#endif /* SCORE_PTP_COMMON_MOCK_H */ diff --git a/src/ptpd/tests/common/mocks/include/score_ptp_consumer_mock.h b/src/ptpd/tests/common/mocks/include/score_ptp_consumer_mock.h new file mode 100644 index 0000000..a886294 --- /dev/null +++ b/src/ptpd/tests/common/mocks/include/score_ptp_consumer_mock.h @@ -0,0 +1,31 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_CONSUMER_MOCK_H +#define SCORE_PTP_CONSUMER_MOCK_H + +#include "inc/score_ptp_consumer.h" +#include + +class PtpConsumerMock { +public: + // Mock method for TSync_GetCurrentVirtualLocalTime + MOCK_METHOD(TSync_ReturnType, TSync_GetCurrentVirtualLocalTime, + (TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr)); + + // Mock method for TSync_BusSetGlobalTime + MOCK_METHOD(TSync_ReturnType, TSync_BusSetGlobalTime, + (TSync_TimeBaseHandleType timeBaseHandle, + const TSync_TimeStampType* globalTimePtr, + const TSync_UserDataType* userDataPtr, + const TSync_MeasurementType* measureDataPtr, + const TSync_VirtualLocalTimeType* localTimePtr)); + + // Mock method for Score_PtpReadSubTlvFromBuffer + MOCK_METHOD(Score_PtpBooleanType, Score_PtpReadSubTlvFromBuffer, + (const uint8_t *autosarTlvBufferIn)); +}; + +#endif /* SCORE_PTP_CONSUMER_MOCK_H */ diff --git a/src/ptpd/tests/common/mocks/include/score_ptp_provider_mock.h b/src/ptpd/tests/common/mocks/include/score_ptp_provider_mock.h new file mode 100644 index 0000000..c6f24ed --- /dev/null +++ b/src/ptpd/tests/common/mocks/include/score_ptp_provider_mock.h @@ -0,0 +1,28 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_PROVIDER_MOCK_H +#define SCORE_PTP_PROVIDER_MOCK_H + +#include "inc/score_ptp_common.h" +#include + +class PtpProviderMock { +public: + // Mock method for TSync_GetCurrentVirtualLocalTime + MOCK_METHOD(TSync_ReturnType, TSync_GetCurrentVirtualLocalTime, + (TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr)); + + // Mock method for TSync_BusGetGlobalTime + MOCK_METHOD(TSync_ReturnType, TSync_BusGetGlobalTime, + (TSync_TimeBaseHandleType timeBaseHandle, + TSync_TimeStampType* globalTimePtr, + TSync_UserDataType* userDataPtr, + TSync_MeasurementType* measureDataPtr, + TSync_VirtualLocalTimeType* localTimePtr)); + +}; + +#endif /* SCORE_PTP_PROVIDER_MOCK_H */ diff --git a/src/ptpd/tests/common/mocks/include/score_ptp_subtlv_mock.h b/src/ptpd/tests/common/mocks/include/score_ptp_subtlv_mock.h new file mode 100644 index 0000000..770ee02 --- /dev/null +++ b/src/ptpd/tests/common/mocks/include/score_ptp_subtlv_mock.h @@ -0,0 +1,16 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_PTP_SUBTLV_MOCK_H +#define SCORE_PTP_SUBTLV_MOCK_H + +#include "inc/score_ptp_subtlv.h" +#include + +class PtpSubTlvMock { +public: + MOCK_METHOD(uint8, Crc_CalculateCRC8H2F, (const uint8*, uint32, uint8, boolean)); +}; + +#endif /* SCORE_PTP_SUBTLV_MOCK_H */ diff --git a/src/ptpd/tests/tests.rst b/src/ptpd/tests/tests.rst new file mode 100644 index 0000000..8ebe1c9 --- /dev/null +++ b/src/ptpd/tests/tests.rst @@ -0,0 +1,8 @@ +############## +Software Tests +############## + +.. toctree:: + :maxdepth: 1 + + SCORE_UT/unit-tests.rst diff --git a/src/tsync-daemon/BUILD b/src/tsync-daemon/BUILD new file mode 100644 index 0000000..6999bc9 --- /dev/null +++ b/src/tsync-daemon/BUILD @@ -0,0 +1,107 @@ +# src/score/time/daemon/BUILD + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("@flatbuffers//:build_defs.bzl", "flatbuffer_cc_library") + +cc_library( + name = "arg_parser", + srcs = ["src/ArgParser.cpp"], + hdrs = ["src/ArgParser.h"], + strip_include_prefix = "src", + visibility = [ + ":__subpackages__", + ], +) + +cc_library( + name = "house_keeping", + srcs = ["src/HouseKeeping.cpp"], + hdrs = ["src/HouseKeeping.h"], + strip_include_prefix = "src", + visibility = [ + ":__subpackages__", + ], +) + +cc_library( + name = "time_base_configuration", + srcs = ["src/TimeBaseConfiguration.cpp"], + hdrs = ["src/TimeBaseConfiguration.h"], + strip_include_prefix = "src", + deps = [ + "//src/tsync-utility-lib:tsync_utility_if", + ], + visibility = [ + ":__subpackages__", + ], +) + +cc_library( + name = "worker", + srcs = [ + "src/ConfigLoader.h", + "src/HouseKeeping.h", + "src/TimeBaseConfiguration.h", + "src/TsyncWorker.cpp" + ], + hdrs = ["src/TsyncWorker.h"], + strip_include_prefix = "src", + deps = [ + "//src/tsync-lib:tsync_if", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common", + ], + visibility = [ + ":__subpackages__", + ], +) + +cc_library( + name = "tsync_daemon_lib", + srcs = glob([ + "src/**/*.cpp", + "src/**/*.h", + ]), + copts = ["-iquote src/tsync-daemon/src"], + deps = [ + ":tsync_flatcfg", + "//src/tsync-utility-lib:tsync_utility", + "//src/flatcfg:flatcfg", + "//src/common", + "@score_baselibs//score/result:result", + ], + linkstatic = True, + visibility = [ + "//tests/common/mocks:__subpackages__", + ], +) + +flatbuffer_cc_library( + name = "tsync_flatcfg", + srcs = ["src/tsync_flatcfg.fbs"], +) + +genrule( + name = "config_file", + srcs = [ + "src/tsync_flatcfg.fbs", + "src/tsync__Machine_flatcfg.json", + ], + outs = ["src/tsync__Machine_flatcfg.bin"], + cmd = """ + $(location @flatbuffers//:flatc) --binary -o $(RULEDIR)/src/ $(SRCS) + """, + message = "Generating daemon_config binary", + tools = ["@flatbuffers//:flatc"], +) + +cc_binary( + name = "tsync_daemon", + deps = [ + ":tsync_daemon_lib", + ], + data = [ + ":config_file", + ], + visibility = ["//visibility:public"], +) diff --git a/src/tsync-daemon/src/ArgParser.cpp b/src/tsync-daemon/src/ArgParser.cpp new file mode 100644 index 0000000..5b76f21 --- /dev/null +++ b/src/tsync-daemon/src/ArgParser.cpp @@ -0,0 +1,61 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "ArgParser.h" + +#include +#include + +namespace score { +namespace time { +namespace daemon { + +// coverity[exn_spec_violation] There will never be enough arguments for a length_error to be thrown +ArgParser::ArgParser(int arg_count, const char* const arg_values[]) noexcept { + for (int i = 0; i < arg_count; ++i) { + args_.push_back({arg_values[i]}); + } +} + +bool ArgParser::IsDebugEnabled() const noexcept { + return (FindOption("-d") != ArgParser::npos_); +} + +bool ArgParser::IsHelpRequested() const noexcept { + return (FindOption("-h") != ArgParser::npos_); +} + +bool ArgParser::IsVersionRequested() const noexcept { + return (FindOption("-v") != ArgParser::npos_) || (FindOption("--version") != ArgParser::npos_); +} + +ArgParser::distance_type ArgParser::FindOption(std::string_view option) const noexcept { + for (auto it = args_.begin(); it != args_.end(); ++it) { + if (*it == option) { + return std::distance(args_.begin(), it); + } + } + + return -1; +} + +void ArgParser::PrintHelp() noexcept { + static const char* help = + "timesync daemon options:\n" + "----------------------------------------------------------------------------------------------------\n\n" + "-d : [optional] debug mode, will yield more output from the daemon.\n" + "-h : print this help screen.\n\n" + "-v, --version : print the version of the daemon.\n\n" + "----------------------------------------------------------------------------------------------------\n"; + + std::cout << help; +} + +void ArgParser::PrintVersion() noexcept { + std::cout << "Timesync Daemon Version: 0.0.0" << std::endl; +} + +} // namespace daemon +} // namespace time +} // namespace score diff --git a/src/tsync-daemon/src/ArgParser.h b/src/tsync-daemon/src/ArgParser.h new file mode 100644 index 0000000..71e03a5 --- /dev/null +++ b/src/tsync-daemon/src/ArgParser.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_DAEMON_ARGPARSER_H_ +#define SCORE_TIME_DAEMON_ARGPARSER_H_ + +#include +#include +#include + +namespace score { +namespace time { +namespace daemon { + +class ArgParser final { +public: + ArgParser(int arg_count, const char* const arg_values []) noexcept; + ~ArgParser() = default; + + bool IsDebugEnabled() const noexcept; + bool IsHelpRequested() const noexcept; + bool IsVersionRequested() const noexcept; + + static void PrintHelp() noexcept; + static void PrintVersion() noexcept; + +private: + using container_type = std::vector; + using distance_type = std::iterator_traits::difference_type; + + distance_type FindOption(std::string_view option) const noexcept; + std::vector args_; + static constexpr int npos_ = -1; +}; + +} // namespace daemon +} // namespace time +} // namespace score + +#endif // SCORE_TIME_DAEMON_ARGPARSER_H_ diff --git a/src/tsync-daemon/src/ConfigLoader.cpp b/src/tsync-daemon/src/ConfigLoader.cpp new file mode 100644 index 0000000..70ae6e6 --- /dev/null +++ b/src/tsync-daemon/src/ConfigLoader.cpp @@ -0,0 +1,432 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "ConfigLoader.h" + +#include +#include +#include +#include + +#include "score/result/result.h" + +#include "flatcfg/flatcfg.h" +#include "score/time/common/Abort.h" + +#include "TimeBaseConfiguration.h" +#include "tsync_flatcfg_addon.h" +#include "src/tsync-daemon/tsync_flatcfg_generated.h" + +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { +namespace daemon { + +ConfigLoader::ConfigLoader() { + flatcfg::FlatCfg cfg(flatbuffers::score::TSYNC{}); + auto cluster_list_result{cfg.getSwClusterList()}; + if (!cluster_list_result) { + logFatalAndAbort((std::string{"tsync daemon - ConfigLoader: getSwClusterList() failed: "} + + cluster_list_result.error().Message().data()).c_str()); + } + + auto& cl{*cluster_list_result}; + if (cl.empty()) { + logFatalAndAbort("tsync daemon - ConfigLoader: cluster list is empty."); + } + + auto& cluster_to_load{cl.front()}; + + auto load_result{cfg.load(cluster_to_load)}; + if (!load_result) { + logFatalAndAbort((std::string{"tsync daemon - ConfigLoader: load(\""} + cluster_to_load + + "\") failed: " + load_result.error().Message().data()).c_str()); + } + + ParseDomainConfig(*load_result); +} + +void ConfigLoader::ParseDomainConfig(std::shared_ptr cfg) { + auto ecu_cfg{GetEcuCfg(cfg)}; + auto domains{ecu_cfg->globalTimeDomain()}; + + if (domains != nullptr) { + // With the AUTOSAR extensions, there's 16 time domains (0-15) and + // 16 offset time domains (16-31). That's the current understanding. + constexpr uint32_t kTimeDomainIdMax{31u}; + auto& cfg_instance{TimeBaseConfiguration::GetInstance()}; + for (auto domain : *domains) { + TimeBaseConfigData cfg_data; + auto short_name{domain->shortName()}; + // short_name can't be nullptr since ShortNames must be non-empty + if (short_name != nullptr) { + cfg_data.timebase_name = short_name->c_str(); + } + + auto domain_id{domain->domainId()}; + + if (domain_id > kTimeDomainIdMax) { + std::string msg( + "tsync daemon - ConfigLoader::ParseDomainConfig: invalid domain id set for domain = "); + msg += cfg_data.timebase_name; + logFatalAndAbort(msg.c_str()); + } + + cfg_data.timebase_config.domain_id = static_cast(domain_id); + + auto dur{std::chrono::duration(domain->syncLossTimeout())}; + cfg_data.timebase_config.sync_loss_timeout = std::chrono::duration_cast(dur); + + dur = std::chrono::duration(domain->debounceTime()); + cfg_data.timebase_config.debounce_time = std::chrono::duration_cast(dur); + + auto eth_domain_config{domain->ethGlobalTimeDomainProps()}; + if (eth_domain_config == nullptr || eth_domain_config->size() == 0U) { + std::string msg( + "tsync daemon - ConfigLoader::ParseDomainConfig: ethernet global time properties not set for domain = "); + msg += cfg_data.timebase_name; + logFatalAndAbort(msg.c_str()); + } + + ParseEthernetDomainConfig(cfg_data.timebase_name, *eth_domain_config->Get(0U), + cfg_data.timebase_config.eth_time_domain); + + auto provider_config{domain->synchronizedTimeBaseProviders()}; + if (provider_config && provider_config->size() != 0U) { + ParseProviderConfig(cfg_data.timebase_name, *provider_config->Get(0U), + cfg_data.timebase_config.provider_config); + } + + auto consumer_config{domain->synchronizedTimeBaseConsumers()}; + if (consumer_config && consumer_config->size() != 0U) { + ParseConsumerConfig(cfg_data.timebase_name, *consumer_config->Get(0U), + cfg_data.timebase_config.consumer_config); + } + + cfg_instance.AddConfigData(cfg_data); + } + } else { + logFatalAndAbort("tsync daemon - ConfigLoader::ParseDomainConfig: No time domains found."); + } +} + +void ConfigLoader::ParseEthernetDomainConfig(const std::string& domain_name, + const TSYNCFlatBuffer::EthGlobalTimeDomainProps& cfg, + TsyncEthTimeDomain& out) { + switch (cfg.messageCompliance()) { + case TSYNCFlatBuffer::EthGlobalTimeMessageFormatEnum::EthGlobalTimeMessageFormatEnum_IEEE802_1AS: + out.message_format = TsyncEthMessageFormat::kIeee8021AS; + break; + case TSYNCFlatBuffer::EthGlobalTimeMessageFormatEnum::EthGlobalTimeMessageFormatEnum_IEEE802_1AS_AUTOSAR: + out.message_format = TsyncEthMessageFormat::kIeee8021ASAutosar; + break; + default: { + std::string msg( + "tsync daemon - ConfigLoader::ParseEthernetDomainConfig: unknown messageCompliance enum set for EthGlobalTimeDomainProps for time domain = "); + msg += domain_name; + logFatalAndAbort(msg.c_str()); + } + } + + auto str{cfg.destinationPhysicalAddress()}; + + if (str != nullptr) { + std::size_t len{std::min(str->size(), kMacAddressStringLength) + 1U}; + std::memcpy(&out.dest_mac_address, str->c_str(), len); + } + + auto data_id_list{cfg.fupDataIdList()}; + + if (data_id_list != nullptr) { + if (data_id_list->size() > 0U) { + if (data_id_list->size() == kMaxFupDataIdEntries) { + std::memcpy(out.fup_data_id_list, data_id_list->data(), kMaxFupDataIdEntries); + out.num_fup_data_id_entries = kMaxFupDataIdEntries; + } else { + std::string msg( + "tsync daemon - ConfigLoader::ParseEthernetDomainConfig: The number of fupDataIdList entries must be either 0 or 16 for time domain = "); + msg += domain_name; + + logFatalAndAbort(msg.c_str()); + } + } else { + out.num_fup_data_id_entries = 0U; + } + } else { + out.num_fup_data_id_entries = 0U; + } + + out.vlan_priority = cfg.vlanPriority(); + + auto crc_flags{cfg.crcFlags()}; + if (crc_flags != nullptr) { + out.crcFlags.correction_field = (*crc_flags)[0u]->crcCorrectionField(); + out.crcFlags.domain_number = (*crc_flags)[0u]->crcDomainNumber(); + out.crcFlags.message_length = (*crc_flags)[0u]->crcMessageLength(); + out.crcFlags.precise_origin_timestamp = (*crc_flags)[0u]->crcPreciseOriginTimestamp(); + out.crcFlags.sequence_id = (*crc_flags)[0u]->crcSequenceId(); + out.crcFlags.source_port_identity = (*crc_flags)[0u]->crcSourcePortIdentity(); + } + + out.is_valid = true; +} + +void ConfigLoader::ParseConsumerConfig(const std::string& domain_name, + const TSYNCFlatBuffer::SynchronizedTimeBaseConsumer& cfg, + TsyncConsumerConfig& out) { + auto short_name{cfg.shortName()}; + // short_name can't be nullptr since ShortNames must be non-empty + if (short_name != nullptr) { + out.name = short_name->c_str(); + } + + auto nw_consumer{cfg.networkTimeConsumer()}; + if (!nw_consumer) { + std::cerr << "ConfigLoader::ParseProviderConfig(): Network consumer not configured!" << std::endl; + return; + } + + // with new dsl, SynchronizedTimeBaseConsumer instance can only be generated/recored when it is referenced by slave. + // Therefore it is not referenced, it doesn't exist => no need to check nullptr for nw_provider + + switch (nw_consumer->crcValidated()) { + case TSYNCFlatBuffer::GlobalTimeCrcValidationEnum::GlobalTimeCrcValidationEnum_CrcValidated: + out.time_slave_config.crc_validation = TsyncCrcValidation::kValidated; + break; + case TSYNCFlatBuffer::GlobalTimeCrcValidationEnum::GlobalTimeCrcValidationEnum_CrcNotValidated: + out.time_slave_config.crc_validation = TsyncCrcValidation::kNotValidated; + break; + case TSYNCFlatBuffer::GlobalTimeCrcValidationEnum::GlobalTimeCrcValidationEnum_CrcIgnored: + out.time_slave_config.crc_validation = TsyncCrcValidation::kIgnored; + break; + case TSYNCFlatBuffer::GlobalTimeCrcValidationEnum::GlobalTimeCrcValidationEnum_CrcOptional: + out.time_slave_config.crc_validation = TsyncCrcValidation::kOptional; + break; + default: { + std::string msg( + "tsync daemon - ConfigLoader::ParseConsumerConfig: unknown crcValidated enum set for networkTimeConsumer for time domain = "); + msg += domain_name; + logFatalAndAbort(msg.c_str()); + } + } + + out.time_slave_config.time_leap_healing_counter = nw_consumer->timeLeapHealingCounter(); + auto dur{std::chrono::duration(nw_consumer->followUpTimeoutValue())}; + out.time_slave_config.follow_up_timeout_value = std::chrono::duration_cast(dur); + + dur = std::chrono::duration(nw_consumer->timeLeapFutureThreshold()); + out.time_slave_config.time_leap_future_threshold = std::chrono::duration_cast(dur); + dur = std::chrono::duration(nw_consumer->timeLeapPastThreshold()); + out.time_slave_config.time_leap_past_threshold = std::chrono::duration_cast(dur); + + out.time_slave_config.global_time_sequence_counter_jump_width = nw_consumer->globalTimeSequenceCounterJumpWidth(); + + auto sub_tlv_cfg_vec = nw_consumer->subTlvConfig(); + if (sub_tlv_cfg_vec != nullptr) { + auto sub_tlv_cfg = (*sub_tlv_cfg_vec)[0u]; + out.time_slave_config.sub_tlv_config.status_enabled = sub_tlv_cfg->statusSubTlv(); + out.time_slave_config.sub_tlv_config.time_enabled = sub_tlv_cfg->timeSubTlv(); + out.time_slave_config.sub_tlv_config.user_data_enabled = sub_tlv_cfg->userDataSubTlv(); + } + + out.time_slave_config.is_valid = true; +} + +void ConfigLoader::ParseProviderConfig(const std::string& domain_name, + const TSYNCFlatBuffer::SynchronizedTimeBaseProvider& cfg, + TsyncProviderConfig& out) { + auto short_name{cfg.shortName()}; + // short_name can't be nullptr since ShortNames must be non-empty + if (short_name != nullptr) { + out.name = short_name->c_str(); + } + + auto nw_provider{cfg.networkTimeProvider()}; + if (!nw_provider) { + std::cerr << "ConfigLoader::ParseProviderConfig(): Network provider not configured for time domain = " + << domain_name << std::endl; + return; + } + + // with new dsl, SynchronizedTimeBaseProvider instance can only be generated/recored when it is referenced by + // master. Therefore it is not referenced, it doesn't exist => no need to check nullptr for nw_provider + + auto dur{std::chrono::duration(nw_provider->immediateResumeTime())}; + out.time_master_config.immediate_resume_time = std::chrono::duration_cast(dur); + dur = std::chrono::duration(nw_provider->syncPeriod()); + out.time_master_config.sync_period = std::chrono::duration_cast(dur); + out.time_master_config.is_system_wide_global_time_master = nw_provider->isSystemWideGlobalTimeMaster(); + out.time_master_config.is_valid = true; + + switch (nw_provider->crcSecured()) { + case TSYNCFlatBuffer::GlobalTimeCrcSupportEnum::GlobalTimeCrcSupportEnum_CrcSupported: + out.time_master_config.crc_support = TsyncCrcSupport::kSupported; + break; + case TSYNCFlatBuffer::GlobalTimeCrcSupportEnum::GlobalTimeCrcSupportEnum_CrcNotSupported: + out.time_master_config.crc_support = TsyncCrcSupport::kNotSupported; + break; + default: { + std::string msg( + "tsync daemon - ConfigLoader::ParseProviderConfig: crcSecured not set for networkTimeProvider for time domain = "); + msg += domain_name; + logFatalAndAbort(msg.c_str()); + } + } + + auto ts_correction_vec{cfg.timeSyncCorrection()}; + if (ts_correction_vec != nullptr && ts_correction_vec->size() == 1U) { + auto ts_correction{ts_correction_vec->Get(0u)}; + out.time_sync_correction_config.allow_provider_rate_correction = ts_correction->allowProviderRateCorrection(); + out.time_sync_correction_config.num_rate_corrections_per_measurement_duration = + ts_correction->rateCorrectionsPerMeasurementDuration(); + dur = std::chrono::duration(ts_correction->offsetCorrectionAdaptionInterval()); + out.time_sync_correction_config.offset_correction_adaption_interval = + std::chrono::duration_cast(dur); + dur = std::chrono::duration(ts_correction->offsetCorrectionJumpThreshold()); + out.time_sync_correction_config.offset_correction_jump_threshold = + std::chrono::duration_cast(dur); + dur = std::chrono::duration(ts_correction->rateDeviationMeasurementDuration()); + out.time_sync_correction_config.rate_deviation_measurement_duration = + std::chrono::duration_cast(dur); + + auto sub_tlv_cfg_vec = nw_provider->subTlvConfig(); + if (sub_tlv_cfg_vec != nullptr) { + auto sub_tlv_cfg = (*sub_tlv_cfg_vec)[0u]; + out.time_master_config.sub_tlv_config.status_enabled = sub_tlv_cfg->statusSubTlv(); + out.time_master_config.sub_tlv_config.time_enabled = sub_tlv_cfg->timeSubTlv(); + out.time_master_config.sub_tlv_config.user_data_enabled = sub_tlv_cfg->userDataSubTlv(); + } + + out.time_sync_correction_config.is_valid = true; + } else { + out.time_sync_correction_config.is_valid = false; + } +} + +// coverity[autosar_cpp14_a8_4_11_violation] shared_ptr's lifetime is not supposed to be affected here. +const TSYNCFlatBuffer::TSYNCEcuCfg* ConfigLoader::GetEcuCfg(std::shared_ptr cfg) { + auto ecu_cfg{TSYNCFlatBuffer::GetTSYNCEcuCfg(cfg.get())}; + if (ecu_cfg == nullptr) { + logFatalAndAbort("tsync daemon - ConfigLoader::GetModuleInstantiation: GetTSYNCEcuCfg returned nullptr."); + } else { + } + + return ecu_cfg; +} + +void ConfigLoader::DumpConfig() noexcept { + auto& cfg_instance{TimeBaseConfiguration::GetInstance()}; + + for (auto& it : cfg_instance) { + std::cout << "Timebase name: " << it.second.timebase_name << std::endl; + std::cout << "Timebase id: " << static_cast(it.second.timebase_config.domain_id) << std::endl; + std::cout << "Syncloss timeout: " << it.second.timebase_config.sync_loss_timeout.count() << "ms" << std::endl; + std::cout << "--------------------------------- Consumer Config ----------------------------------" + << std::endl; + if (it.second.timebase_config.consumer_config.time_slave_config.is_valid) { + std::cout << "Consumer Name: " << it.second.timebase_config.consumer_config.name << std::endl; + std::cout << "time_leap_healing_counter: " + << it.second.timebase_config.consumer_config.time_slave_config.time_leap_healing_counter + << std::endl; + std::cout << "time_leap_future_threshold: " + << it.second.timebase_config.consumer_config.time_slave_config.time_leap_future_threshold.count() + << "ms" << std::endl; + std::cout << "time_leap_past_threshold: " + << it.second.timebase_config.consumer_config.time_slave_config.time_leap_past_threshold.count() + << "ms" << std::endl; + std::cout << "follow_up_timeout_value: " + << it.second.timebase_config.consumer_config.time_slave_config.follow_up_timeout_value.count() + << "ms" << std::endl; + std::cout << "subtlv status: " + << (it.second.timebase_config.consumer_config.time_slave_config.sub_tlv_config.status_enabled + ? "true" + : "false") + << std::endl; + std::cout << "subtlv time: " + << (it.second.timebase_config.consumer_config.time_slave_config.sub_tlv_config.time_enabled + ? "true" + : "false") + << std::endl; + std::cout << "subtlv user data: " + << (it.second.timebase_config.consumer_config.time_slave_config.sub_tlv_config.user_data_enabled + ? "true" + : "false") + << std::endl; + std::cout + << "global_time_sequence_counter_jump_width: " + << it.second.timebase_config.consumer_config.time_slave_config.global_time_sequence_counter_jump_width + << std::endl; + } else { + std::cout << "- None -" << std::endl; + } + std::cout << "--------------------------------- /Consumer Config ---------------------------------" + << std::endl; + std::cout << "--------------------------------- Provider Config ----------------------------------" + << std::endl; + if (it.second.timebase_config.provider_config.time_master_config.is_valid) { + std::cout << "Provider Name: " << it.second.timebase_config.provider_config.name << std::endl; + std::cout << "is_system_wide_global_time_master: " + << (it.second.timebase_config.provider_config.time_master_config.is_system_wide_global_time_master + ? "true" + : "false") + << std::endl; + std::cout << "immediate_resume_time: " + << it.second.timebase_config.provider_config.time_master_config.immediate_resume_time.count() + << "ms" << std::endl; + std::cout << "sync_period: " + << it.second.timebase_config.provider_config.time_master_config.sync_period.count() << "ms" + << std::endl; + std::cout + << "allow_provider_rate_correction: " + << (it.second.timebase_config.provider_config.time_sync_correction_config.allow_provider_rate_correction + ? "true" + : "false") + << std::endl; + std::cout << "rate_deviation_measurement_duration: " + << it.second.timebase_config.provider_config.time_sync_correction_config + .rate_deviation_measurement_duration.count() + << "ms" << std::endl; + std::cout << "num_rate_corrections_per_measurement_duration: " + << it.second.timebase_config.provider_config.time_sync_correction_config + .num_rate_corrections_per_measurement_duration + << std::endl; + std::cout << "offset_correction_adaption_interval: " + << it.second.timebase_config.provider_config.time_sync_correction_config + .offset_correction_adaption_interval.count() + << "ms" << std::endl; + std::cout << "offset_correction_jump_threshold: " + << it.second.timebase_config.provider_config.time_sync_correction_config + .offset_correction_jump_threshold.count() + << "ms" << std::endl; + std::cout << "subtlv status: " + << (it.second.timebase_config.provider_config.time_master_config.sub_tlv_config.status_enabled + ? "true" + : "false") + << std::endl; + std::cout << "subtlv time: " + << (it.second.timebase_config.provider_config.time_master_config.sub_tlv_config.time_enabled + ? "true" + : "false") + << std::endl; + std::cout << "subtlv user data: " + << (it.second.timebase_config.provider_config.time_master_config.sub_tlv_config.user_data_enabled + ? "true" + : "false") + << std::endl; + } else { + std::cout << "- None -" << std::endl; + } + std::cout << "--------------------------------- /Provider Config ---------------------------------" + << std::endl; + } +} + +const TimeBaseConfiguration& ConfigLoader::GetConfig() const noexcept { + return TimeBaseConfiguration::GetInstance(); +} + +} // namespace daemon +} // namespace time +} // namespace score diff --git a/src/tsync-daemon/src/ConfigLoader.h b/src/tsync-daemon/src/ConfigLoader.h new file mode 100644 index 0000000..f4374e3 --- /dev/null +++ b/src/tsync-daemon/src/ConfigLoader.h @@ -0,0 +1,59 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_DAEMON_CONFIGLOADER_H_ +#define SCORE_TIME_DAEMON_CONFIGLOADER_H_ + +#include +#include + +namespace TSYNCFlatBuffer { +struct TSYNCEcuCfg; +struct EthGlobalTimeDomainProps; +struct SynchronizedTimeBaseConsumer; +struct SynchronizedTimeBaseProvider; +} + +namespace score { +namespace time { + +struct TsyncEthTimeDomain; +struct TsyncConsumerConfig; +struct TsyncProviderConfig; + +namespace daemon { + +class TimeBaseConfiguration; + +class ConfigLoader final { +public: + ConfigLoader(); + ~ConfigLoader() = default; + ConfigLoader(const ConfigLoader&) = delete; + ConfigLoader& operator=(const ConfigLoader&) = delete; + ConfigLoader(ConfigLoader&&) = delete; + ConfigLoader& operator=(ConfigLoader&&) = delete; + + const TimeBaseConfiguration& GetConfig() const noexcept; + void DumpConfig() noexcept; + +private: + void ParseDomainConfig(std::shared_ptr cfg); + void ParseEthernetDomainConfig(const std::string& domain_name, + const TSYNCFlatBuffer::EthGlobalTimeDomainProps& cfg, + TsyncEthTimeDomain& out); + void ParseConsumerConfig(const std::string& domain_name, + const TSYNCFlatBuffer::SynchronizedTimeBaseConsumer& cfg, + TsyncConsumerConfig& out); + void ParseProviderConfig(const std::string& domain_name, + const TSYNCFlatBuffer::SynchronizedTimeBaseProvider& cfg, + TsyncProviderConfig& out); + const TSYNCFlatBuffer::TSYNCEcuCfg* GetEcuCfg(std::shared_ptr cfg); +}; + +} // namespace daemon +} // namespace time +} // namespace score + +#endif // SCORE_TIME_DAEMON_CONFIGLOADER_H_ diff --git a/src/tsync-daemon/src/HouseKeeping.cpp b/src/tsync-daemon/src/HouseKeeping.cpp new file mode 100644 index 0000000..9c451c9 --- /dev/null +++ b/src/tsync-daemon/src/HouseKeeping.cpp @@ -0,0 +1,27 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "HouseKeeping.h" + +namespace score { +namespace time { +namespace daemon { + +void SignalHandler(int /*signal*/) { + // This will end the run-loop in the worker instance + HouseKeeping::exit_flag_ = 1; +} + +void HouseKeeping::Init() noexcept { + exit_flag_ = 0; + (void)std::signal(SIGINT, SignalHandler); + (void)std::signal(SIGTERM, SignalHandler); +} + +// set by signal handler, checked by worker +volatile std::sig_atomic_t HouseKeeping::exit_flag_ = 0; + +} // namespace daemon +} // namespace time +} // namespace score diff --git a/src/tsync-daemon/src/HouseKeeping.h b/src/tsync-daemon/src/HouseKeeping.h new file mode 100644 index 0000000..86990ee --- /dev/null +++ b/src/tsync-daemon/src/HouseKeeping.h @@ -0,0 +1,28 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_DAEMON_HOUSEKEEPING_H_ +#define SCORE_TIME_DAEMON_HOUSEKEEPING_H_ + +#include + +namespace score { +namespace time { +namespace daemon { + +class HouseKeeping { +public: + static void Init() noexcept; + + // This flag will be set when an exit signal was received + static volatile std::sig_atomic_t exit_flag_; + + HouseKeeping() = delete; +}; + +} // namespace daemon +} // namespace time +} // namespace score + +#endif // SCORE_TIME_DAEMON_HOUSEKEEPING_H_ diff --git a/src/tsync-daemon/src/TimeBaseConfiguration.cpp b/src/tsync-daemon/src/TimeBaseConfiguration.cpp new file mode 100644 index 0000000..85e3b88 --- /dev/null +++ b/src/tsync-daemon/src/TimeBaseConfiguration.cpp @@ -0,0 +1,58 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "TimeBaseConfiguration.h" + +namespace score { +namespace time { +namespace daemon { + +TimeBaseConfiguration& TimeBaseConfiguration::GetInstance() noexcept { + // clang-format off + // RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "The scope of this object is limited to this function. It is therefore protected during global static initializiation.", true) + static TimeBaseConfiguration instance{}; + // clang-format on + return instance; +} + +void TimeBaseConfiguration::AddConfigData(const TimeBaseConfigData& config_data) noexcept { + configurations_[config_data.timebase_name] = config_data; +} + +std::optional TimeBaseConfiguration::GetConfigData(const std::string_view& name) const noexcept { + auto it = configurations_.find(std::string(name)); + if (it != configurations_.end()) { + return std::optional(it->second); + } + + return std::optional(); +} + +void TimeBaseConfiguration::Clear() { + configurations_.clear(); +} + +TimeBaseConfiguration::iterator TimeBaseConfiguration::begin() { + return configurations_.begin(); +} + +TimeBaseConfiguration::const_iterator TimeBaseConfiguration::cbegin() const { + return configurations_.cbegin(); +} + +TimeBaseConfiguration::iterator TimeBaseConfiguration::end() { + return configurations_.end(); +} + +TimeBaseConfiguration::const_iterator TimeBaseConfiguration::cend() const { + return configurations_.cend(); +} + +std::size_t TimeBaseConfiguration::size() const { + return configurations_.size(); +} + +} // namespace daemon +} // namespace time +} // namespace score diff --git a/src/tsync-daemon/src/TimeBaseConfiguration.h b/src/tsync-daemon/src/TimeBaseConfiguration.h new file mode 100644 index 0000000..d5eb007 --- /dev/null +++ b/src/tsync-daemon/src/TimeBaseConfiguration.h @@ -0,0 +1,56 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_DAEMON_TIMEBASECONFIGURATION_H_ +#define SCORE_TIME_DAEMON_TIMEBASECONFIGURATION_H_ + +#include +#include +#include +#include + +#include "score/time/utility/TsyncConfigTypes.h" + +namespace score { +namespace time { +namespace daemon { + +struct TimeBaseConfigData { + std::string timebase_name{}; + TsyncTimeDomainConfig timebase_config{}; +}; + +class TimeBaseConfiguration final { +public: + using iterator = std::map::iterator; + using const_iterator = std::map::const_iterator; + + static TimeBaseConfiguration& GetInstance() noexcept; + ~TimeBaseConfiguration() = default; + TimeBaseConfiguration(const TimeBaseConfiguration&) = delete; + TimeBaseConfiguration& operator=(const TimeBaseConfiguration&) = delete; + TimeBaseConfiguration(TimeBaseConfiguration&&) = delete; + TimeBaseConfiguration& operator=(TimeBaseConfiguration&&) = delete; + + void AddConfigData(const TimeBaseConfigData& config_data) noexcept; + std::optional GetConfigData(const std::string_view& name) const noexcept; + void Clear(); + iterator begin(); + const_iterator cbegin() const; + iterator end(); + const_iterator cend() const; + std::size_t size() const; + +private: + TimeBaseConfiguration() noexcept = default; + +private: + std::map configurations_{}; +}; + +} // namespace daemon +} // namespace time +} // namespace score + +#endif // SCORE_TIME_DAEMON_TIMEBASECONFIGURATION_H_ diff --git a/src/tsync-daemon/src/TsyncWorker.cpp b/src/tsync-daemon/src/TsyncWorker.cpp new file mode 100644 index 0000000..468059f --- /dev/null +++ b/src/tsync-daemon/src/TsyncWorker.cpp @@ -0,0 +1,248 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "TsyncWorker.h" + +#include + +#include +#include +#include +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/ITimeBaseReader.h" +#include "score/time/utility/ITimeBaseWriter.h" +#include "score/time/utility/SysCalls.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncSharedUtils.h" + +#include "ConfigLoader.h" +#include "HouseKeeping.h" +#include "TimeBaseConfiguration.h" + +using score::time::common::logFatalAndAbort; +using SteadyClock = std::chrono::steady_clock; + +namespace score { +namespace time { +namespace daemon { + +TsyncWorker::TsyncWorker() noexcept { +} + +TsyncWorker::~TsyncWorker() noexcept { + ShutDown(); +} + +// coverity[exn_spec_violation] There will never be enough transmission semaphores for a length_error to be thrown +void TsyncWorker::Init() noexcept { + if (!is_initialized_) { + InitDaemon(); + HouseKeeping::Init(); + InitIdMappings(); + InitTimeBaseAccessors(); + is_initialized_ = true; + } else { + // TODO: log stuff + std::cerr << "tsyncd: tsyncd: TsyncWorker::Init - TsyncWorker already initialized\n"; + } + std::cout << "tsyncd: TsyncWorker::Init - done.\n"; +} + +void TsyncWorker::Run() noexcept { + std::cout << "tsyncd: TsyncWorker::Run - enter\n"; + if (!is_initialized_) { + // TODO: log stuff + logFatalAndAbort("tsyncd: TsyncWorker::Run - TsyncWorker::Init must be called before TsyncWorker::Run"); + } + std::cout << "tsyncd: TsyncWorker::Run - entering loop\n"; + while (HouseKeeping::exit_flag_ == 0) { + CheckTimeouts(); + std::this_thread::sleep_until(SteadyClock::now() + std::chrono::seconds(1)); + } + + std::cout << "tsyncd: TsyncWorker::Run - received exit signal\n"; +} + +void TsyncWorker::CheckTimeouts() { + auto& cfg = TimeBaseConfiguration::GetInstance(); + std::size_t i = 0u; + + for (auto it = cfg.begin(); it != cfg.end() && i < time_base_writers_.size(); ++it, ++i) { + // check if we have a valid consumer config + // timeouts are only relevant for consumers + if (!it->second.timebase_config.consumer_config.time_slave_config.is_valid) { + continue; + } + // check if we were ever synchronized and if the timeout state has already been set. + // We may only switch to timeout state if we were synchronized at some point. + // Resetting the timeout state will be done by ptpd, so if the flag is already set, + // we do nothing + score::time::SynchronizationStatus timeBaseStatus = GetTimestampStatus(time_base_readers_[i]); + if (timeBaseStatus != SynchronizationStatus::kSynchronized) { + continue; + } + auto ts_update_time{GetTimestampLastUpdateTime(time_base_readers_[i])}; + auto ts_current_time{GetTimestampCurrentVlt()}; + if (ts_current_time < ts_update_time) { + continue; + } + + auto vlt_diff = ts_current_time - ts_update_time; + if (it->second.timebase_config.sync_loss_timeout < vlt_diff) { + // coverity[autosar_cpp14_m0_1_3_violation:FALSE] #54006: Do not need to use the variables + // defined by lock_guard. + std::lock_guard lock(*time_base_writers_[i]); + if (!time_base_writers_[i]->Write(SynchronizationStatus::kTimeOut)) { + std::stringstream ss; + ss << "TsyncWorker::CheckTimeouts() : Writing updated timeout status failed for " + << time_base_writers_[i]->GetAccessor().GetName(); + std::cerr << ss.str().c_str() << std::endl; + } else { + } + } + } +} + +std::chrono::nanoseconds TsyncWorker::GetTimestampLastUpdateTime( + TimeBaseReaderFactory::PointerType& tb_reader) { + std::chrono::nanoseconds res{0}; + // coverity[autosar_cpp14_m0_1_3_violation:FALSE] #54006: Do not need to use the variables defined by lock_guard. + std::lock_guard lock{*tb_reader}; + if (AlignedSkip(*tb_reader)) { + if (tb_reader->Read(res)) { + } else { + std::cerr << "TsyncWorker::GetTimestampLastUpdateTime() : Read virtual local time failed\n"; + } + } else { + logFatalAndAbort("TsyncWorker::GetTimestampLastUpdateTime() : Skip failed."); + } + + return res; +} + +std::chrono::nanoseconds TsyncWorker::GetTimestampCurrentVlt() { + std::chrono::nanoseconds res{0}; + + auto vlt = TsyncSharedUtils::GetCurrentVirtualLocalTime(); + if (vlt) { + res = *vlt; + } + + return res; +} + +score::time::SynchronizationStatus TsyncWorker::GetTimestampStatus( + TimeBaseReaderFactory::PointerType& tb_reader) { + SynchronizationStatus status{SynchronizationStatus::kNotSynchronizedUntilStartup}; + // coverity[autosar_cpp14_m0_1_3_violation:FALSE] #54006: Do not need to use the variables defined by lock_guard. + std::lock_guard lock{*tb_reader}; + bool res = tb_reader->Read(status); + if (!res) { + std::cerr << "TsyncWorker::GetTimestampStatus(" << tb_reader->GetAccessor().GetName() + << "): failed to read status." << std::endl; + status = SynchronizationStatus::kNotSynchronizedUntilStartup; + } + + return status; +} + +void TsyncWorker::ShutDown() noexcept { + if (is_initialized_) { + CloseTimeBaseAccessors(); + transmission_semaphores_.clear(); + is_initialized_ = false; + } +} + +void TsyncWorker::InitDaemon() { + const mode_t mask = 007u; + (void)OsUmask(mask); +} + +void TsyncWorker::InitTimeBaseAccessors() { + auto& cfg = TimeBaseConfiguration::GetInstance(); + + std::size_t i = 0u; + for (auto it = cfg.begin(); it != cfg.end() && i < time_base_writers_.size(); ++it, ++i) { + time_base_writers_[i] = TimeBaseWriterFactory::Create(it->first, true); + time_base_readers_[i] = TimeBaseReaderFactory::Create(it->first); + time_base_writers_[i]->GetAccessor().Open(); + time_base_readers_[i]->GetAccessor().Open(); + + // create transmission semaphore for timebase providers + if (it->second.timebase_config.provider_config.time_master_config.is_valid) { + mode_t temp_umask = OsUmask(0111u); + transmission_semaphores_.push_back( + TsyncSharedUtils::CreateTransmissionSemaphore(it->second.timebase_config.domain_id, true)); + (void)OsUmask(temp_umask); + } + + { + // coverity[autosar_cpp14_m0_1_3_violation:FALSE] #54006: Do not need to use the variables defined by + // lock_guard. + std::lock_guard lock(*time_base_writers_[i]); + time_base_writers_[i]->WriteDefaults(); + time_base_writers_[i]->Write(it->second.timebase_config); + } + } +} + +void TsyncWorker::InitIdMappings() { + id_mappings_handler_ = std::make_unique(); + auto& cfg = TimeBaseConfiguration::GetInstance(); + + for (auto& it : cfg) { + auto res = id_mappings_handler_->AddDomainMapping(it.second.timebase_config.domain_id, + it.second.timebase_name.c_str()); + if (res) { + if (it.second.timebase_config.consumer_config.time_slave_config.is_valid) { + // TODO: We currently only allow one consumer per time domain + res = id_mappings_handler_->AddConsumerToDomain(it.second.timebase_config.domain_id, + it.second.timebase_config.consumer_config.name); + if (!res) { + logFatalAndAbort("TsyncWorker::InitIdMappings(): Error adding consumer to time domain mapping."); + } + } + if (it.second.timebase_config.provider_config.time_master_config.is_valid) { + res = id_mappings_handler_->AddProviderToDomain(it.second.timebase_config.domain_id, + it.second.timebase_config.provider_config.name); + if (!res) { + logFatalAndAbort("TsyncWorker::InitIdMappings(): Error adding provider to time domain mapping."); + } + } + } else { + logFatalAndAbort("TsyncWorker::InitIdMappings(): Error adding domain mapping."); + } + } + + id_mappings_handler_->CommitMappingsToSharedMemory(); +} + +void TsyncWorker::CloseTimeBaseAccessors() { + id_mappings_handler_.reset(); + + for (auto& p : time_base_writers_) { + if (!p) { + break; + } + + p.reset(); + } + + for (auto& p : time_base_readers_) { + if (!p) { + break; + } + + p.reset(); + } +} + +} // namespace daemon +} // namespace time +} // namespace score diff --git a/src/tsync-daemon/src/TsyncWorker.h b/src/tsync-daemon/src/TsyncWorker.h new file mode 100644 index 0000000..4e67c0e --- /dev/null +++ b/src/tsync-daemon/src/TsyncWorker.h @@ -0,0 +1,61 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_DAEMON_TSYNCWORKER_H_ +#define SCORE_TIME_DAEMON_TSYNCWORKER_H_ + +#include +#include +#include +#include + +#include "score/time/synchronized_time_base_status.h" +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TimeBaseWriterFactory.h" +#include "score/time/utility/TsyncNamedSemaphore.h" + +namespace score { +namespace time { + +class TsyncIdMappingsHandler; + +namespace daemon { + +// note: this class is currently not implemented in a thread-safe fashion +class TsyncWorker final { +public: + TsyncWorker() noexcept; + ~TsyncWorker() noexcept; + TsyncWorker(const TsyncWorker&) = delete; + TsyncWorker& operator=(const TsyncWorker&) = delete; + + void Init() noexcept; + void Run() noexcept; + void ShutDown() noexcept; + +private: + void InitDaemon(); + void InitIdMappings(); + void InitTimeBaseAccessors(); + void CloseTimeBaseAccessors(); + void CheckTimeouts(); + std::chrono::nanoseconds GetTimestampLastUpdateTime(TimeBaseReaderFactory::PointerType& tb_reader); + std::chrono::nanoseconds GetTimestampCurrentVlt(); + SynchronizationStatus GetTimestampStatus(TimeBaseReaderFactory::PointerType& tb_reader); + + using TimePoint = std::chrono::time_point; + +private: + std::unique_ptr id_mappings_handler_{}; + std::array time_base_writers_{}; + std::array time_base_readers_{}; + std::vector transmission_semaphores_{}; + bool is_initialized_ = false; +}; + +} // namespace daemon +} // namespace time +} // namespace score + +#endif // SCORE_TIME_DAEMON_TSYNCWORKER_H_ diff --git a/src/tsync-daemon/src/entrypoint.cpp b/src/tsync-daemon/src/entrypoint.cpp new file mode 100644 index 0000000..b742609 --- /dev/null +++ b/src/tsync-daemon/src/entrypoint.cpp @@ -0,0 +1,17 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/common/ExcludeCoverageAdapter.h" + +EXCLUDE_COVERAGE_START( + "Main function wrapper used for testing tsyncd_main with Unit Tests, it does not make sense to write unit tests for it.") + +extern int tsyncd_main(int argc, char* argv[]); + +// coverity[autosar_cpp14_a15_3_3_violation] No exceptions will occur. +int main(int argc, char* argv[]) { + tsyncd_main(argc, argv); +} + +EXCLUDE_COVERAGE_END \ No newline at end of file diff --git a/src/tsync-daemon/src/tsync__Machine_flatcfg.json b/src/tsync-daemon/src/tsync__Machine_flatcfg.json new file mode 100644 index 0000000..e44f977 --- /dev/null +++ b/src/tsync-daemon/src/tsync__Machine_flatcfg.json @@ -0,0 +1,167 @@ +{ + "functionCluster": "tsync", + "versionMajor": 3, + "versionMinor": 1, + "globalTimeDomain": [ + { + "shortName": "sysclock", + "domainId": 1, + "ethGlobalTimeDomainProps": [ + { + "crcFlags": [ + { + "crcCorrectionField": true, + "crcDomainNumber": true, + "crcMessageLength": true, + "crcPreciseOriginTimestamp": true, + "crcSequenceId": true, + "crcSourcePortIdentity": true + } + ], + "destinationPhysicalAddress": "80:3F:5D:09:7A:86", + "fupDataIdList": [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + ], + "messageCompliance": 0, + "vlanPriority": 2 + } + ], + "globalTimeEthMaster": [ + { + "shortName": "GlobalTimeEthMaster", + "communicationConnector": { + "shortName": "EthernetCommunicationConnector", + "createEcuWakeupSource": false, + "dynamicPncToChannelMappingEnabled": false, + "pncFilterArrayMasks": [1], + "pncGatewayType": 0, + "maximumTransmissionUnit": 1500, + "neighborCacheSize": 1024, + "pathMtuEnabled": false, + "pathMtuTimeout": 0.2 + }, + "immediateResumeTime": 1.0, + "isSystemWideGlobalTimeMaster": true, + "syncPeriod": 1.0, + "crcSecured": 0, + "holdOverTime": 60.0, + "subTlvConfig": [ + { + "statusSubTlv": false, + "timeSubTlv": true, + "userDataSubTlv": true + } + ] + } + ], + "icvFreshnessValueId": 1, + "maxProgressionMismatchThreshold": 5.0, + "globalTimeEthSlave": [ + { + "shortName": "GlobalTimeEthSlave", + "subTlvConfig": [ + { + "statusSubTlv": false, + "timeSubTlv": true, + "userDataSubTlv": true + } + ], + "communicationConnector": { + "shortName": "EthernetCommunicationConnector", + "createEcuWakeupSource": false, + "dynamicPncToChannelMappingEnabled": false, + "pncFilterArrayMasks": [1], + "pncGatewayType": 0, + "maximumTransmissionUnit": 1500, + "neighborCacheSize": 1024, + "pathMtuEnabled": false, + "pathMtuTimeout": 0.2 + }, + "followUpTimeoutValue": 5.0, + "timeLeapFutureThreshold": 2.4, + "timeLeapHealingCounter": 3, + "timeLeapPastThreshold": 2.5, + "crcValidated": 0 + } + ], + "synchronizedTimeBaseConsumers": [ + { + "shortName": "consumer", + "networkTimeConsumer": { + "shortName": "GlobalTimeEthSlave", + "subTlvConfig": [ + { + "statusSubTlv": false, + "timeSubTlv": true, + "userDataSubTlv": true + } + ], + "communicationConnector": { + "shortName": "EthernetCommunicationConnector", + "createEcuWakeupSource": false, + "dynamicPncToChannelMappingEnabled": false, + "pncFilterArrayMasks": [1], + "pncGatewayType": 0, + "maximumTransmissionUnit": 1500, + "neighborCacheSize": 1024, + "pathMtuEnabled": false, + "pathMtuTimeout": 0.2 + }, + "followUpTimeoutValue": 5.0, + "timeLeapFutureThreshold": 2.4, + "timeLeapHealingCounter": 3, + "timeLeapPastThreshold": 2.5, + "crcValidated": 0 + } + } + ], + "synchronizedTimeBaseProviders": [ + { + "shortName": "provider", + "networkTimeProvider": { + "shortName": "GlobalTimeEthMaster", + "communicationConnector": { + "shortName": "EthernetCommunicationConnector", + "createEcuWakeupSource": false, + "dynamicPncToChannelMappingEnabled": false, + "pncFilterArrayMasks": [1], + "pncGatewayType": 0, + "maximumTransmissionUnit": 1500, + "neighborCacheSize": 1024, + "pathMtuEnabled": false, + "pathMtuTimeout": 0.2 + }, + "immediateResumeTime": 1.0, + "isSystemWideGlobalTimeMaster": true, + "syncPeriod": 1.0, + "crcSecured": 0, + "holdOverTime": 60.0, + "subTlvConfig": [ + { + "statusSubTlv": false, + "timeSubTlv": true, + "userDataSubTlv": true + } + ] + }, + "timeSyncCorrection": [ + ] + } + ], + "syncLossTimeout": 1.0 + } + ], + "ethernetCommunicationConnector": [ + { + "shortName": "EthernetCommunicationConnector", + "createEcuWakeupSource": false, + "dynamicPncToChannelMappingEnabled": false, + "pncFilterArrayMasks": [1], + "pncGatewayType": 0, + "maximumTransmissionUnit": 1500, + "neighborCacheSize": 1024, + "pathMtuEnabled": false, + "pathMtuTimeout": 0.2 + } + ] +} diff --git a/src/tsync-daemon/src/tsync_flatcfg.fbs b/src/tsync-daemon/src/tsync_flatcfg.fbs new file mode 100644 index 0000000..74e52e8 --- /dev/null +++ b/src/tsync-daemon/src/tsync_flatcfg.fbs @@ -0,0 +1,131 @@ +namespace TSYNCFlatBuffer; + +// TSYNC version 4.0 + +file_identifier "BTSY"; +file_extension "bin"; + +enum EthGlobalTimeMessageFormatEnum : byte { + IEEE802_1AS = 0, // IEEE802-1AS + IEEE802_1AS_AUTOSAR = 1 // IEEE802-1AS-AUTOSAR +} + +enum GlobalTimeCrcSupportEnum : byte { + CrcSupported = 0, // CRC-SUPPORTED + CrcNotSupported = 1 // CRC-NOT-SUPPORTED +} + +enum GlobalTimeCrcValidationEnum : byte { + CrcValidated = 0, // CRC-VALIDATED + CrcNotValidated = 1, // CRC-NOT-VALIDATED + CrcIgnored = 2, // CRC-IGNORED + CrcOptional = 3 // CRC-OPTIONAL +} + +enum PncGatewayTypeEnum : byte { + None = 0, // NONE + Active = 1, // ACTIVE + Passive = 2 // PASSIVE +} + +table TSYNCEcuCfg { + functionCluster: string (id:0); + versionMajor: int (id:1); + versionMinor: int (id:2); + globalTimeDomain: [GlobalTimeDomain] (id:3); + ethernetCommunicationConnector: [EthernetCommunicationConnector] (id:4); +} + +table GlobalTimeDomain { + shortName: string (id:0); + domainId: uint (id:1); + icvFreshnessValueId: uint (id:2); + maxProgressionMismatchThreshold: double (id:3); + syncLossTimeout: double (id:4); + debounceTime: double (id:5); + globalTimeEthMaster: [GlobalTimeEthMaster] (id:6); + globalTimeEthSlave: [GlobalTimeEthSlave] (id:7); + synchronizedTimeBaseProviders: [SynchronizedTimeBaseProvider] (id:8); + synchronizedTimeBaseConsumers: [SynchronizedTimeBaseConsumer] (id:9); + ethGlobalTimeDomainProps: [EthGlobalTimeDomainProps] (id:10); +} + +table EthGlobalTimeDomainProps { + destinationPhysicalAddress: string (id:0); + fupDataIdList: [uint8] (id:1); + messageCompliance: EthGlobalTimeMessageFormatEnum (id:2); + vlanPriority: uint (id:3); + crcFlags: [CrcFlags] (id:4); +} + +table CrcFlags { + crcCorrectionField: bool (id:0); + crcDomainNumber: bool (id:1); + crcMessageLength: bool (id:2); + crcPreciseOriginTimestamp: bool (id:3); + crcSourcePortIdentity: bool (id:4); + crcSequenceId: bool (id:5); +} + +table SynchronizedTimeBaseProvider { + shortName: string (id:0); + networkTimeProvider: GlobalTimeEthMaster (id:1); + timeSyncCorrection: [TimeSyncCorrection] (id:2); +} + +table SynchronizedTimeBaseConsumer { + shortName: string (id:0); + networkTimeConsumer: GlobalTimeEthSlave (id:1); +} + +table GlobalTimeEthMaster { + shortName: string (id:0); + immediateResumeTime: double (id:2); + isSystemWideGlobalTimeMaster: bool (id:3); + syncPeriod: double (id:4); + crcSecured: GlobalTimeCrcSupportEnum (id:5); + holdOverTime: double (id:7); + communicationConnector: EthernetCommunicationConnector (id:1); + subTlvConfig: [SubTlvConfig] (id:6); +} + +table SubTlvConfig { + statusSubTlv: bool (id:0); + timeSubTlv: bool (id:1); + userDataSubTlv: bool (id:2); +} + +table GlobalTimeEthSlave { + shortName: string (id:0); + followUpTimeoutValue: double (id:2); + timeLeapFutureThreshold: double (id:3); + timeLeapHealingCounter: uint (id:4); + timeLeapPastThreshold: double (id:5); + crcValidated: GlobalTimeCrcValidationEnum (id:6); + globalTimeSequenceCounterJumpWidth: uint (id:7); + communicationConnector: EthernetCommunicationConnector (id:1); + subTlvConfig: [SubTlvConfig] (id:8); +} + +table EthernetCommunicationConnector { + shortName: string (id:0); + createEcuWakeupSource: bool (id:1); + pncFilterArrayMasks: [uint] (id:2); + dynamicPncToChannelMappingEnabled: bool (id:3); + maximumTransmissionUnit: uint (id:4); + neighborCacheSize: uint (id:5); + pathMtuEnabled: bool (id:6); + pathMtuTimeout: double (id:7); + pncGatewayType: PncGatewayTypeEnum (id:8); +} + +table TimeSyncCorrection { + allowProviderRateCorrection: bool (id:0); + offsetCorrectionAdaptionInterval: double (id:1); + offsetCorrectionJumpThreshold: double (id:2); + rateCorrectionsPerMeasurementDuration: uint (id:3); + rateDeviationMeasurementDuration: double (id:4); + providerRateDeviationMax: uint (id:5); +} + +root_type TSYNCEcuCfg; diff --git a/src/tsync-daemon/src/tsync_flatcfg_addon.h b/src/tsync-daemon/src/tsync_flatcfg_addon.h new file mode 100644 index 0000000..e286920 --- /dev/null +++ b/src/tsync-daemon/src/tsync_flatcfg_addon.h @@ -0,0 +1,35 @@ +#ifndef FLATBUFFERS_GENERATED_TSYNCFLATCFG_TSYNCFLATBUFFER_ADDON_H_ +#define FLATBUFFERS_GENERATED_TSYNCFLATCFG_TSYNCFLATBUFFER_ADDON_H_ + +#include "flatbuffers/score.h" + +// generated by option: --score-extension +namespace flatbuffers { +namespace score { + +class TSYNC : public FcBase { +private: + /* coverity[autosar_cpp14_a0_1_3_violation] The private method _versionMajor() is used by CRTP class FcBase + * and intentionally not accessible through the user API. */ + static constexpr int32_t _versionMajor() noexcept { + return static_cast(3L); + } + /* coverity[autosar_cpp14_a0_1_3_violation] The private method _versionMinor() is used by CRTP class FcBase + * and intentionally not accessible through the user API. */ + static constexpr int32_t _versionMinor() noexcept { + return static_cast(1L); + } + /* coverity[autosar_cpp14_a0_1_3_violation] The private method _name() is used by CRTP class FcBase and + * intentionally not accessible through the user API. */ + static constexpr const char* _name() noexcept { + return "TSYNC"; + } + /* RULECHECKER_comment(1, 1, check_friend_declaration, "The CRTP class FcBase requires a friend declaration + * to access private methods. However, these methods should not be accessible through the user API.", true) */ + friend FcBase; +}; + +} // namespace score +} // namespace flatbuffers + +#endif diff --git a/src/tsync-daemon/src/tsyncd_main.cpp b/src/tsync-daemon/src/tsyncd_main.cpp new file mode 100644 index 0000000..97b4bed --- /dev/null +++ b/src/tsync-daemon/src/tsyncd_main.cpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include "ArgParser.h" +#include "ConfigLoader.h" +#include "TsyncWorker.h" + +// coverity[autosar_cpp14_a15_3_3_violation] No exceptions will occur. +int tsyncd_main(int argc, char* argv[]) { + std::cout << "Hello World! This is the tsync daemon" << std::endl; + + score::time::daemon::ArgParser argParser(argc, argv); + if (argParser.IsHelpRequested()) { + score::time::daemon::ArgParser::PrintHelp(); + return 0; + } + + if (argParser.IsVersionRequested()) { + score::time::daemon::ArgParser::PrintVersion(); + return 0; + } + + // coverity[autosar_cpp14_m3_4_1_violation] This object is declared at the correct scope. + score::time::daemon::ConfigLoader confLoader; + + if (argParser.IsDebugEnabled()) { + confLoader.DumpConfig(); + } + + score::time::daemon::TsyncWorker worker; + worker.Init(); + worker.Run(); + worker.ShutDown(); + + return 0; +} diff --git a/src/tsync-daemon/test/ArgParser_UT/ArgParser_UT.cpp b/src/tsync-daemon/test/ArgParser_UT/ArgParser_UT.cpp new file mode 100644 index 0000000..75f27fc --- /dev/null +++ b/src/tsync-daemon/test/ArgParser_UT/ArgParser_UT.cpp @@ -0,0 +1,80 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#define private public +#include "ArgParser.h" +#undef private + +using namespace score::time::daemon; + +namespace testing { +namespace daemon_argparser_ut { + +TEST(Daemon_ArgParser, ArgParser_WithEmptyArgs_IsCorrectlyInitialized) { + int argc = 2; + const char* const argv[] = {"one", "two"}; + + ArgParser argParser(argc, argv); + + EXPECT_EQ(argParser.args_.size(), argc); + + int i = 0; + for(auto arg : argParser.args_) { + EXPECT_EQ(arg, argv[i++]); + } +} + +TEST(Daemon_ArgParser, IsDebugEnabled_WithEmptyArgs_ReportsCorrectFlagState) { + int argc = 2; + const char* const argv[] = {"", ""}; + + ArgParser argParser(argc, argv); + EXPECT_FALSE(argParser.IsDebugEnabled()); +} + +TEST(Daemon_ArgParser, IsHelpRequested_WithEmptyArgs_ReportsCorrectFlagState) { + int argc = 2; + const char* const argv[] = {"", ""}; + + ArgParser argParser(argc, argv); + EXPECT_FALSE(argParser.IsHelpRequested()); +} + +TEST(Daemon_ArgParser, IsHelpRequested_WithHelpArg_ReportsCorrectFlagState) { + int argc = 2; + const char* const argv[] = {"", "-h"}; + ArgParser argParser(argc, argv); + EXPECT_TRUE(argParser.IsHelpRequested()); +} + +TEST(Daemon_ArgParser, IsDebugEnabled_WithDebugArg_ReportsCorrectFlagState) { + int argc = 2; + const char* const argv[] = {"", "-d"}; + ArgParser argParser(argc, argv); + EXPECT_TRUE(argParser.IsDebugEnabled()); +} + +TEST(Daemon_ArgParser, IsVersionRequested_WithVersionArg_ReportsCorrectFlagState) { + int argc = 2; + const char* const argv[] = {"", "-v"}; + + ArgParser argParser(argc, argv); + EXPECT_TRUE(argParser.IsVersionRequested()); +} + +TEST(Daemon_ArgParser, IsVersionRequested_WithLongVersionArg_ReportsCorrectFlagState) { + int argc = 2; + const char* const argv[] = {"", "--version"}; + + ArgParser argParser(argc, argv); + EXPECT_TRUE(argParser.IsVersionRequested()); +} + +TEST(Daemon_ArgParser, PrintHelp_Succeeds) { + ArgParser::PrintHelp(); +} + +} // namespace daemon_argparser_ut +} // namespace testing diff --git a/src/tsync-daemon/test/ArgParser_UT/BUILD b/src/tsync-daemon/test/ArgParser_UT/BUILD new file mode 100644 index 0000000..d45a3ff --- /dev/null +++ b/src/tsync-daemon/test/ArgParser_UT/BUILD @@ -0,0 +1,12 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "ArgParser_UT", + srcs = [ + "ArgParser_UT.cpp", + ], + deps = [ + "//src/tsync-daemon:arg_parser", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-daemon/test/HouseKeeping_UT/BUILD b/src/tsync-daemon/test/HouseKeeping_UT/BUILD new file mode 100644 index 0000000..d5d3fef --- /dev/null +++ b/src/tsync-daemon/test/HouseKeeping_UT/BUILD @@ -0,0 +1,12 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "HouseKeeping_UT", + srcs = [ + "HouseKeeping_UT.cpp", + ], + deps = [ + "//src/tsync-daemon:house_keeping", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-daemon/test/HouseKeeping_UT/HouseKeeping_UT.cpp b/src/tsync-daemon/test/HouseKeeping_UT/HouseKeeping_UT.cpp new file mode 100644 index 0000000..d6535f0 --- /dev/null +++ b/src/tsync-daemon/test/HouseKeeping_UT/HouseKeeping_UT.cpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include "HouseKeeping.h" + +using score::time::daemon::HouseKeeping; + +namespace testing { +namespace housekeeping_ut { + +class HouseKeepingFixture : public ::testing::Test { +public: + HouseKeepingFixture() { + HouseKeeping::Init(); + } +}; + +TEST_F(HouseKeepingFixture, Init_HkNotInitialized_ExitFlagIsZero) { + EXPECT_EQ(HouseKeeping::exit_flag_, 0); +} + +TEST_F(HouseKeepingFixture, RaiseSigInt_HkInitialized_ExitFlagIsSet) { + std::raise(SIGINT); + EXPECT_EQ(HouseKeeping::exit_flag_, 1); +} + +TEST_F(HouseKeepingFixture, raiseSigTerm_HkInitialized_ExitFlagIsSet) { + std::raise(SIGTERM); + EXPECT_EQ(HouseKeeping::exit_flag_, 1); +} + +TEST_F(HouseKeepingFixture, raiseSigInt_AlreadyRaisedBefore_ExitFlagIsSet) { + std::raise(SIGINT); + std::raise(SIGINT); + EXPECT_EQ(HouseKeeping::exit_flag_, 1); +} + +TEST_F(HouseKeepingFixture, raiseSigTerm_AlreadyRaisedBefore_ExitFlagIsSet) { + std::raise(SIGTERM); + std::raise(SIGTERM); + EXPECT_EQ(HouseKeeping::exit_flag_, 1); +} + +TEST_F(HouseKeepingFixture, raiseSigInt_SigTermRaisedBefore_ExitFlagIsSet) { + std::raise(SIGTERM); + std::raise(SIGINT); + EXPECT_EQ(HouseKeeping::exit_flag_, 1); +} + +TEST_F(HouseKeepingFixture, raiseSigTerm_SigIntRaisedBefore_ExitFlagIsSet) { + std::raise(SIGINT); + std::raise(SIGTERM); + EXPECT_EQ(HouseKeeping::exit_flag_, 1); +} + +TEST_F(HouseKeepingFixture, raiseSigCont_HkInitialized_ExitFlagIsZero) { + std::raise(SIGCONT); + EXPECT_EQ(HouseKeeping::exit_flag_, 0); +} + +} // namespace housekeeping_ut +} // namespace testing diff --git a/src/tsync-daemon/test/TimeBaseConfiguration_UT/BUILD b/src/tsync-daemon/test/TimeBaseConfiguration_UT/BUILD new file mode 100644 index 0000000..a6a0c7a --- /dev/null +++ b/src/tsync-daemon/test/TimeBaseConfiguration_UT/BUILD @@ -0,0 +1,13 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TimeBaseConfiguration_UT", + srcs = [ + "TimeBaseConfiguration_UT.cpp", + ], + deps = [ + "//src/tsync-daemon:time_base_configuration", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-daemon/test/TimeBaseConfiguration_UT/TimeBaseConfiguration_UT.cpp b/src/tsync-daemon/test/TimeBaseConfiguration_UT/TimeBaseConfiguration_UT.cpp new file mode 100644 index 0000000..4366f96 --- /dev/null +++ b/src/tsync-daemon/test/TimeBaseConfiguration_UT/TimeBaseConfiguration_UT.cpp @@ -0,0 +1,215 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "matcher_operators.h" + +#define private public +// class under test +#include "TimeBaseConfiguration.h" +#undef private + +using namespace score::time; +using namespace score::time::daemon; +using InstanceSpecifier = std::string_view; + +static const char* CONFIG_DATA_NAME = "TestConfig"; + +class TimeBaseConfigurationSizeTestFixture + : public ::testing::TestWithParam, int>> {}; + +// Considered test cases: +// No configuration added - returns size 0. +// One configuration added - returns size 1. +// Two different configurations added - returns size 2. +// Two equal configurations added - returns size 1. +INSTANTIATE_TEST_SUITE_P( + TimeBaseConfigurationSize, TimeBaseConfigurationSizeTestFixture, + ::testing::Values( + std::make_tuple(std::vector{}, 0), std::make_tuple(std::vector{{}}, 1), + std::make_tuple(std::vector{{"1", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 1}}, + {"2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}}, + 2), + std::make_tuple(std::vector{{"1", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 1}}, + {"1", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 1}}}, + 1))); + +namespace testing { +namespace daemon_timebaseconfiguration_ut { + +TEST(Daemon_TimeBaseConfiguration_UT, AddConfigData) { + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + TimeBaseConfigData data; + data.timebase_name = CONFIG_DATA_NAME; + instance.AddConfigData(data); + + auto opt = instance.GetConfigData(CONFIG_DATA_NAME); + ASSERT_TRUE(opt); + auto retrieved_data = *opt; + + EXPECT_EQ(data.timebase_name, retrieved_data.timebase_name); + EXPECT_EQ(data.timebase_config, retrieved_data.timebase_config); +} + +TEST(Daemon_TimeBaseConfiguration_UT, AddMoreConfigData) { + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + TimeBaseConfigData data; + data.timebase_name = CONFIG_DATA_NAME; + data.timebase_config.domain_id = 1; + instance.AddConfigData(data); + + auto opt = instance.GetConfigData(CONFIG_DATA_NAME); + ASSERT_TRUE(opt); + auto retrieved_data = *opt; + + EXPECT_EQ(data.timebase_name, retrieved_data.timebase_name); + EXPECT_EQ(data.timebase_config, retrieved_data.timebase_config); +} + +// Note: This test is not strictly required due to the singleton nature +// of TimeBaseConfiguration. It is covered by the other tests. Better +// to be explicit, though. +TEST(Daemon_TimeBaseConfiguration_UT, Replace) { + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + TimeBaseConfigData data; + data.timebase_name = CONFIG_DATA_NAME; + data.timebase_config.domain_id = 1; + instance.AddConfigData(data); + + auto opt = instance.GetConfigData(CONFIG_DATA_NAME); + ASSERT_TRUE(opt); + auto retrieved_data = *opt; + + EXPECT_EQ(data.timebase_name, retrieved_data.timebase_name); + EXPECT_EQ(data.timebase_config, retrieved_data.timebase_config); + + data.timebase_config.domain_id = 2; + instance.AddConfigData(data); + + opt = instance.GetConfigData(CONFIG_DATA_NAME); + ASSERT_TRUE(opt); + retrieved_data = *opt; + + EXPECT_EQ(data.timebase_name, retrieved_data.timebase_name); + EXPECT_EQ(data.timebase_config, retrieved_data.timebase_config); +} + +// Test whether in case if no configuration was added that begin (cbegin) and +// end (cend) iterators are equal. +TEST(Daemon_TimeBaseConfiguration_UT, Begin_End_ConfigurationEmpty_AreEqual) { + // Arrange + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + instance.Clear(); + + // Act + auto begin_it = instance.begin(); + auto end_it = instance.end(); + auto cbegin_it = instance.cbegin(); + auto cend_it = instance.cend(); + + // Assert + ASSERT_EQ(begin_it, end_it); + ASSERT_EQ(cbegin_it, cend_it); +} + +// Test whether in case if there is configuration was added, the distance between begin (cbegin) +// and end (cend) is equal to amount of configurations. +TEST(Daemon_TimeBaseConfiguration_UT, Begin_End_OneConfig_IteratorsValid) { + // Arrange + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + instance.Clear(); + + TimeBaseConfigData data; + instance.AddConfigData(data); + + // Act + auto begin_it = instance.begin(); + auto end_it = instance.end(); + auto cbegin_it = instance.cbegin(); + auto cend_it = instance.cend(); + + // Assert + ASSERT_EQ(std::distance(begin_it, end_it), 1); + ASSERT_EQ(std::distance(cbegin_it, cend_it), 1); +} + +// Test whether if there is no configuration was added, GetConfigData will return +// default configuration for any name +TEST(Daemon_TimeBaseConfiguration_UT, GetConfigData_NonExistingName_ReturnsDefaultConfiguration) { + // Arrange + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + instance.Clear(); + + // Act + auto opt = instance.GetConfigData("1"); + ASSERT_FALSE(opt); +} + +// Test whether if there is no configuration was added, GetConfigData will return +// default configuration for any instance specifier +TEST(Daemon_TimeBaseConfiguration_UT, GetConfigData_NonExistingInstanceSpecifier_ReturnsDefaultConfiguration) { + // Arrange + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + instance.Clear(); + + InstanceSpecifier config_specifier("mytest/config"); + + // Act + auto res = instance.GetConfigData(config_specifier); + + // Assert + ASSERT_FALSE(res); +} + +// Test whether in case if there is configuration was added, GetConfigData will return +// correct configuration by InstanceSpecifier +TEST(Daemon_TimeBaseConfiguration_UT, GetConfigData_ByInstanceSpecifier_ReturnsCorrectConfiguration) { + // Arrange + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + instance.Clear(); + InstanceSpecifier config_specifier("mytest/config"); + TimeBaseConfigData expected_config{std::string(config_specifier), + TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 1}}; + + instance.AddConfigData(expected_config); + + // Act + auto opt = instance.GetConfigData(config_specifier); + ASSERT_TRUE(opt); + auto config = *opt; + + // Assert + ASSERT_EQ(config.timebase_name, expected_config.timebase_name); + ASSERT_EQ(config.timebase_config, expected_config.timebase_config); +} + +// Test that size return correct value based on amount of added configurations. +TEST_P(TimeBaseConfigurationSizeTestFixture, Size_DifferentConfigs_ReturnsCorrectValue) { + // Arrange + TimeBaseConfiguration& instance = TimeBaseConfiguration::GetInstance(); + instance.Clear(); + + auto parameters = GetParam(); + auto& configs = std::get<0>(parameters); + auto& expected_result = std::get<1>(parameters); + + for (auto& config : configs) { + instance.AddConfigData(config); + } + + // Act + auto size = instance.size(); + + // Assert + ASSERT_EQ(size, expected_result); +} + +} // namespace daemon_timebaseconfiguration_ut +} // namespace testing diff --git a/src/tsync-daemon/test/Worker_UT/BUILD b/src/tsync-daemon/test/Worker_UT/BUILD new file mode 100644 index 0000000..1744b45 --- /dev/null +++ b/src/tsync-daemon/test/Worker_UT/BUILD @@ -0,0 +1,19 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "Worker_UT", + srcs = [ + "Worker_UT.cpp", + ], + deps = [ + "//src/tsync-daemon:worker", + "//src/tsync-daemon:house_keeping", + "//src/tsync-daemon:time_base_configuration", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/mocks:utility_tsync_shared_utils_mock", + "//src/tsync-utility-lib:utility_tsync_id_mappings_handler", + "//src/tsync-utility-lib:utility_tsync_named_semaphore", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-daemon/test/Worker_UT/Worker_UT.cpp b/src/tsync-daemon/test/Worker_UT/Worker_UT.cpp new file mode 100644 index 0000000..d27b786 --- /dev/null +++ b/src/tsync-daemon/test/Worker_UT/Worker_UT.cpp @@ -0,0 +1,514 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include +#include +#include + +#include "score/time/utility/TsyncIdMappingsHandler.h" + +#include "SharedMemTimeBaseReaderMock.h" +#include "SharedMemTimeBaseWriterMock.h" +#include "SysCallsNamedSemMock.h" +#include "SysCallsMiscMock.h" +#include "TimeBaseReaderFactoryMock.h" +#include "TimeBaseWriterFactoryMock.h" +#include "TsyncSharedUtilsMock.h" + +#include "HouseKeeping.h" +#include "TimeBaseConfiguration.h" + +#define private public +#include "TsyncWorker.h" +#undef public + +using namespace score::time; +using namespace std::chrono_literals; + +using score::time::daemon::HouseKeeping; +using score::time::daemon::TimeBaseConfigData; +using score::time::daemon::TimeBaseConfiguration; + +using ::testing::_; +using ::testing::An; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgReferee; + +namespace testing { +namespace daemon_worker_ut { + +class DaemonWorkerFixture : public ::testing::Test { +public: + static const int32_t EXIT_CODE; + +protected: + void SetUp() override { + writer_factory_mock_return_real_writer = false; + reader_factory_mock_return_real_reader = false; + shared_utils_mock = std::make_unique<::testing::NiceMock>(); + reader_factory_mock = std::make_unique<::testing::NiceMock>(); + writer_factory_mock = std::make_unique<::testing::NiceMock>(); + misc_mock = std::make_unique<::testing::NiceMock>(); + named_semaphore_mock = std::make_unique<::testing::NiceMock>(); + + TimeBaseConfigData cfg{"TEST", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 1}}; + cfg.timebase_config.provider_config.time_master_config.is_valid = true; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg); + + // install abort handler for our death tests + std::signal(SIGABRT, &AbortHandler); + // As we use singleton mock objects, clear expectations after each test + ::testing::Mock::AllowLeak(misc_mock.get()); + ::testing::Mock::AllowLeak(shared_utils_mock.get()); + ::testing::Mock::AllowLeak(reader_factory_mock.get()); + ::testing::Mock::AllowLeak(writer_factory_mock.get()); + } + + void TearDown() override { + CleanUp(); + std::signal(SIGABRT, SIG_DFL); + } + + static void CleanUp() { + TimeBaseConfiguration::GetInstance().Clear(); + + // these mocks have to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + reader_factory_mock.reset(); + writer_factory_mock.reset(); + misc_mock.reset(); + shared_utils_mock.reset(); + worker_.ShutDown(); + } + + static void AbortHandler(int /*signal*/) noexcept { + CleanUp(); + std::exit(EXIT_CODE); + } + + static score::time::daemon::TsyncWorker worker_; +}; + + score::time::daemon::TsyncWorker DaemonWorkerFixture::worker_{}; + +const int32_t DaemonWorkerFixture::EXIT_CODE = 1; + +TEST_F(DaemonWorkerFixture, Init_NotInitialized_Success) { + // Arrange + InSequence seq; + + // Act + worker_.Init(); + + // Assert + EXPECT_TRUE(worker_.is_initialized_); +} + +TEST_F(DaemonWorkerFixture, Init_AlreadyInitialized_Success) { + // Arrange + InSequence seq; + + worker_.Init(); + + // Act and Assert + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); +} + +TEST_F(DaemonWorkerFixture, Init_OnCommitMappingsToSharedMemoryFailure_Aborts) { + ASSERT_EXIT( + { + // Arrange + auto writer_mock = writer_factory_mock->Create(kIdMappingsShmemFileName, kIdMappingsShmemSize, true); + auto raw_writer_mock = static_cast(writer_mock.get()); + ::testing::Mock::AllowLeak(raw_writer_mock); + EXPECT_CALL(*raw_writer_mock, Write(::testing::An())).WillOnce(::testing::Return(false)); + EXPECT_CALL(*writer_factory_mock, Create(::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(testing::ByMove(std::move(writer_mock)))); + + // Act + worker_.Init(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing time domain mapping"); +} + +TEST_F(DaemonWorkerFixture, Init_OnAddDomainMappingFailure_Aborts) { + // Arrange + // Same domain id as in SetUp + TimeBaseConfigData cfg{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 1}}; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg); + + ASSERT_EXIT( + { + auto writer_mock = writer_factory_mock->Create(kIdMappingsShmemFileName, kIdMappingsShmemSize, true); + auto raw_writer_mock = static_cast(writer_mock.get()); + ::testing::Mock::AllowLeak(raw_writer_mock); + EXPECT_CALL(*raw_writer_mock, Write(::testing::An())).WillOnce(::testing::Return(false)); + EXPECT_CALL(*writer_factory_mock, Create(::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(testing::ByMove(std::move(writer_mock)))); + + // Act + worker_.Init(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error adding domain mapping"); +} + +TEST_F(DaemonWorkerFixture, Run_RunUntilExit_Success) { + // Arrange + InSequence seq; + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + // Assert + // This should make the Run method finish immediately + HouseKeeping::exit_flag_ = 1; + worker_.Run(); + SharedMemTimeBaseWriterMock* mock_ptr = + static_cast(worker_.time_base_writers_[0].get()); + EXPECT_CALL(*mock_ptr, Close()).Times(1); + worker_.ShutDown(); +} + +TEST_F(DaemonWorkerFixture, Run_WithTimeoutCheck_Succeeds) { + // Arrange + TimeBaseConfigData cfg_valid{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid.timebase_config.consumer_config.time_slave_config.is_valid = true; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid); + InSequence seq; + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + HouseKeeping::exit_flag_ = 0; + + std::thread exit_toggler_thread([]() { + std::this_thread::sleep_for(5000ms); + HouseKeeping::exit_flag_ = 1; + }); + + exit_toggler_thread.detach(); + worker_.Run(); + SharedMemTimeBaseWriterMock* mock_ptr = + static_cast(worker_.time_base_writers_[0].get()); + EXPECT_CALL(*mock_ptr, Close()).Times(1); + worker_.ShutDown(); +} + +TEST_F(DaemonWorkerFixture, Run_WithTimeStampSkipError_Aborts) { + // Arrange + TimeBaseConfigData cfg_valid{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid.timebase_config.consumer_config.time_slave_config.is_valid = true; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid); + + ASSERT_EXIT( + { + InSequence seq; + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + HouseKeeping::exit_flag_ = 0; + + std::thread exit_toggler_thread([]() { + std::this_thread::sleep_for(5000ms); + HouseKeeping::exit_flag_ = 1; + }); + + exit_toggler_thread.detach(); + auto raw_reader_mock = static_cast(worker_.time_base_readers_[1].get()); + EXPECT_CALL(*raw_reader_mock, SetPosition(_)).WillOnce(::testing::Return(false)); + worker_.Run(); + }, + ::testing::ExitedWithCode(EXIT_FAILURE), "Skip failed"); +} + +TEST_F(DaemonWorkerFixture, GetTimestampCurrentVlt_Fails) { + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(std::nullopt)); + auto res = worker_.GetTimestampCurrentVlt(); + worker_.ShutDown(); + EXPECT_EQ(res.count(), 0); +} + +TEST_F(DaemonWorkerFixture, GetTimestampLastUpdateTime_Succeeds) { + auto p = TimeBaseReaderFactory::Create("TEST"); + SharedMemTimeBaseReaderMock* mock = static_cast(p.get()); + EXPECT_CALL(*mock, SetPosition(_)).WillOnce(::testing::Return(true)); + EXPECT_CALL(*mock, Read(::testing::An())).WillOnce(::testing::Return(true)); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + worker_.GetTimestampLastUpdateTime(p); +} + +TEST_F(DaemonWorkerFixture, GetTimestampLastUpdateTime_Read_Fails) { + auto p = TimeBaseReaderFactory::Create("TEST"); + SharedMemTimeBaseReaderMock* mock = static_cast(p.get()); + EXPECT_CALL(*mock, SetPosition(_)).WillOnce(::testing::Return(true)); + EXPECT_CALL(*mock, Read(::testing::An())).WillOnce(::testing::Return(false)); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + worker_.GetTimestampLastUpdateTime(p); +} + +TEST_F(DaemonWorkerFixture, Run_CallRunBeforeInit_Aborts) { + // Assert + ASSERT_EXIT( + { + // Act + worker_.Run(); + }, + ::testing::ExitedWithCode(EXIT_CODE), + "tsyncd: TsyncWorker::Run - TsyncWorker::Init must be called before TsyncWorker::Run"); +} + +TEST_F(DaemonWorkerFixture, ShutDown_Return_Success) { + // This test verifies that the process returns from ShutDown(). + // Act + worker_.ShutDown(); + + // Assert + // ShutDown() doesn't have return value. Make success with SUCCEED(). + SUCCEED(); +} + +TEST_F(DaemonWorkerFixture, InitIdMappings_OnAddConsumerFailure_Exit) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid1.timebase_config.consumer_config.name = "consumer"; + + TimeBaseConfigData cfg_valid2{"TEST3", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 3}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid2.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid2.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid2.timebase_config.consumer_config.name = "consumer"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid2); + + ASSERT_EXIT({ worker_.InitIdMappings(); }, ::testing::ExitedWithCode(EXIT_CODE), + "Error adding consumer to time domain mapping"); +} + +TEST_F(DaemonWorkerFixture, InitIdMappings_OnAddProviderFailure_Exit) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.provider_config.time_master_config.is_valid = true; + cfg_valid1.timebase_config.provider_config.name = "provider"; + + TimeBaseConfigData cfg_valid2{"TEST3", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 3}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid2.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid2.timebase_config.provider_config.time_master_config.is_valid = true; + cfg_valid2.timebase_config.provider_config.name = "provider"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid2); + + ASSERT_EXIT({ worker_.InitIdMappings(); }, ::testing::ExitedWithCode(EXIT_CODE), + "Error adding provider to time domain mapping"); +} + +TEST_F(DaemonWorkerFixture, CheckTimeouts_GetTimestampStatusReturnsInvalidStatus_Succeeds) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(-1); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid1.timebase_config.consumer_config.name = "consumer"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + auto reader_mock = static_cast(worker_.time_base_readers_[1].get()); + + EXPECT_CALL(*reader_mock, lock()).Times(1); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(Return(false)); + EXPECT_CALL(*reader_mock, unlock()).Times(1); + worker_.CheckTimeouts(); + + SUCCEED(); +} + +TEST_F(DaemonWorkerFixture, CheckTimeouts_NoTimeDiff_Succeeds) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(0); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid1.timebase_config.consumer_config.name = "consumer"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + auto reader_mock = static_cast(worker_.time_base_readers_[1].get()); + + score::time::VirtualLocalTime vlt_to_inject(0); + + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kSynchronized), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(vlt_to_inject), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + worker_.CheckTimeouts(); + + SUCCEED(); +} + +TEST_F(DaemonWorkerFixture, CheckTimeouts_UpdateimeIsGreater_Succeeds) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(0); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid1.timebase_config.consumer_config.name = "consumer"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + auto reader_mock = static_cast(worker_.time_base_readers_[1].get()); + + score::time::VirtualLocalTime vlt_to_inject(123456); + + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()) + .WillOnce(Return(std::chrono::nanoseconds(12345))); + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kSynchronized), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(vlt_to_inject), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + worker_.CheckTimeouts(); + + SUCCEED(); +} + +TEST_F(DaemonWorkerFixture, CheckTimeouts_OnWriteTimeBaseStatusFailure_Fails) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(0); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid1.timebase_config.consumer_config.name = "consumer"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + score::time::VirtualLocalTime vlt_to_inject1(1000000); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + auto reader_mock = static_cast(worker_.time_base_readers_[1].get()); + auto writer_mock = static_cast(worker_.time_base_writers_[1].get()); + + score::time::SynchronizationStatus status_to_inject = + score::time::SynchronizationStatus::kSynchronized; // global sync + score::time::VirtualLocalTime vlt_to_inject2(0); + + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject1)); + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(vlt_to_inject2), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + + EXPECT_CALL(*writer_mock, lock()).Times(1); + EXPECT_CALL(*writer_mock, Write(An())).WillOnce(Return(false)); + EXPECT_CALL(*writer_mock, unlock()).Times(1); + + worker_.CheckTimeouts(); + + SUCCEED(); +} + +TEST_F(DaemonWorkerFixture, CheckTimeouts_OnWriteTimeBaseStatusSuccess_Succeed) { + // Arrange + TimeBaseConfigData cfg_valid1{"TEST2", TsyncTimeDomainConfig{{}, {}, {}, {}, {}, 2}}; + + // The negative timeout value forces the timeout check in TsyncWorker::CheckTimeouts to kick in + cfg_valid1.timebase_config.sync_loss_timeout = std::chrono::milliseconds(0); + // We require a valid consumer config for this test + cfg_valid1.timebase_config.consumer_config.time_slave_config.is_valid = true; + cfg_valid1.timebase_config.consumer_config.name = "consumer"; + TimeBaseConfiguration::GetInstance().AddConfigData(cfg_valid1); + score::time::VirtualLocalTime vlt_to_inject1(1000000); + + // Act + worker_.Init(); + EXPECT_TRUE(worker_.is_initialized_); + + auto reader_mock = static_cast(worker_.time_base_readers_[1].get()); + auto writer_mock = static_cast(worker_.time_base_writers_[1].get()); + ::testing::Mock::AllowLeak(reader_mock); + ::testing::Mock::AllowLeak(writer_mock); + + score::time::SynchronizationStatus status_to_inject = + score::time::SynchronizationStatus::kSynchronized; // global sync + score::time::VirtualLocalTime vlt_to_inject2(0); + + std::string_view reader_name("/reader_one"); + + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject1)); + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(vlt_to_inject2), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + + EXPECT_CALL(*writer_mock, lock()).Times(1); + EXPECT_CALL(*writer_mock, Write(An())).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, unlock()).Times(1); + + worker_.CheckTimeouts(); + + SUCCEED(); +} + +} // namespace daemon_worker_ut +} // namespace testing diff --git a/src/tsync-lib/BUILD b/src/tsync-lib/BUILD new file mode 100644 index 0000000..c9df57e --- /dev/null +++ b/src/tsync-lib/BUILD @@ -0,0 +1,105 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "tsync_if", + hdrs = glob([ + "include/**/*.h", + ]), + strip_include_prefix = "include", + deps = [ + "@score_baselibs//score/result", + "@score_baselibs//score/language/futurecpp:futurecpp", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "synchronized_time_base_common", + srcs = [ + "src/SynchronizedTimeBaseCommon.cpp" + ], + hdrs = ["src/SynchronizedTimeBaseCommon.h"], + strip_include_prefix = "src", + deps = [ + ":tsync_if", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common", + "@score_baselibs//score/language/futurecpp:futurecpp", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "synchronized_time_base_consumer_impl", + srcs = [ + "src/SynchronizedTimeBaseCommon.h", + "src/SynchronizedTimeBaseConsumerImpl.cpp", + "src/TimeBaseStatusAccessMediator.h", + ], + deps = [ + ":tsync_if", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "synchronized_time_base_provider_impl", + srcs = [ + "src/SynchronizedTimeBaseCommon.h", + "src/SynchronizedTimeBaseProviderImpl.cpp", + ], + deps = [ + ":tsync_if", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common", + "@score_baselibs//score/result", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "synchronized_time_base_status_impl", + srcs = [ + "src/SynchronizedTimeBaseStatusImpl.cpp", + ], + hdrs = [ + "src/TimeBaseStatusAccessMediator.h", + ], + strip_include_prefix = "src", + deps = [ + ":tsync_if", + "//src/tsync-utility-lib:tsync_utility_if", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "tsync", + hdrs = [ + ":tsync_if", + ], + srcs = [ + ":synchronized_time_base_consumer_impl", + ":synchronized_time_base_provider_impl", + ":synchronized_time_base_common", + ":synchronized_time_base_status_impl", + ], + deps = [ + ":tsync_if", + "//src/tsync-utility-lib:tsync_utility", + "//src/common", + #"@score_baselibs//score/mw/log", + "@score_baselibs//score/language/futurecpp:futurecpp", + ], + visibility = ["//visibility:public"], +) diff --git a/src/tsync-lib/include/score/time/consumer_time_base_validation_notification.h b/src/tsync-lib/include/score/time/consumer_time_base_validation_notification.h new file mode 100644 index 0000000..158ec5e --- /dev/null +++ b/src/tsync-lib/include/score/time/consumer_time_base_validation_notification.h @@ -0,0 +1,33 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +/// @brief A callback interface for consumer notification + +#ifndef SCORE_TIME_CONSUMER_TIME_BASE_VALIDATION_NOTIFICATION_H_ +#define SCORE_TIME_CONSUMER_TIME_BASE_VALIDATION_NOTIFICATION_H_ + +#include "score/time/time_validation_measurement_types.h" + +namespace score { +namespace time { + +/// @traceid{SWS_TS_00428} +class ConsumerTimeBaseValidationNotification { +public: + /// @traceid{SWS_TS_01300} + virtual ~ConsumerTimeBaseValidationNotification() = default; + + /// @param[in] measurementData Detailed timing data for the pDelay Initiator + /// @traceid{SWS_TS_00422} + virtual void SetPdelayInitiatorData(const PdelayInitiatorMeasurementType& measurementData) = 0; + + /// @param[in] measurementData Detailed data for validation of the Time Slave + /// @traceid{SWS_TS_00420} + virtual void SetSlaveTimingData(const TimeSlaveMeasurementType& measurementData) = 0; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CONSUMER_TIME_BASE_VALIDATION_NOTIFICATION_H_ diff --git a/src/tsync-lib/include/score/time/provider_time_base_validation_notification.h b/src/tsync-lib/include/score/time/provider_time_base_validation_notification.h new file mode 100644 index 0000000..1a10ce7 --- /dev/null +++ b/src/tsync-lib/include/score/time/provider_time_base_validation_notification.h @@ -0,0 +1,33 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +/// @brief A callback interface for validator notification + +#ifndef SCORE_TIME_PROVIDER_TIME_BASE_VALIDATION_NOTIFICATION_H_ +#define SCORE_TIME_PROVIDER_TIME_BASE_VALIDATION_NOTIFICATION_H_ + +#include "score/time/time_validation_measurement_types.h" + +namespace score { +namespace time { + +/// @traceid{SWS_TS_00419} +class ProviderTimeBaseValidationNotification { +public: + /// @traceid{SWS_TS_01301} + virtual ~ProviderTimeBaseValidationNotification() = default; + + /// @param[in] measurementData Detailed timing data for the pDelay Responder + /// @traceid{SWS_TS_00423} + virtual void SetPdelayResponderData(const PdelayResponderMeasurementType& measurementData) = 0; + + /// @param[in] measurementData Detailed data for validation of the Time Master + /// @traceid{SWS_TS_00421} + virtual void SetMasterTimingData(const TimeMasterMeasurementType& measurementData) = 0; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_PROVIDER_TIME_BASE_VALIDATION_NOTIFICATION_H_ diff --git a/src/tsync-lib/include/score/time/synchronized_time_base_consumer.h b/src/tsync-lib/include/score/time/synchronized_time_base_consumer.h new file mode 100644 index 0000000..af8be24 --- /dev/null +++ b/src/tsync-lib/include/score/time/synchronized_time_base_consumer.h @@ -0,0 +1,149 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +/// @brief Synchronized Time Base Consumer class + +#ifndef SCORE_TIME_SYNCHRONIZED_TIME_BASE_CONSUMER_H_ +#define SCORE_TIME_SYNCHRONIZED_TIME_BASE_CONSUMER_H_ + +#include +#include +#include + +#include "score/time/consumer_time_base_validation_notification.h" +#include "score/time/synchronized_time_base_status.h" +#include "score/time/time_precision_measurement_type.h" +#include "score/time/timestamp.h" + +namespace score { +namespace time { + +class ITimeBaseReader; + +/** + * @traceid{SWS_TS_01000} + */ +class SynchronizedTimeBaseConsumer final { +public: + /** + * @param instance_specifier String representation of an score::mw::com::InstanceSpecifier to + * an PortPrototype of an TimeSynchronizationInterface + * @traceid{SWS_TS_01001} + */ + explicit SynchronizedTimeBaseConsumer(const std::string_view& instance_specifier) noexcept; + + /** + * @traceid{SWS_TS_01002} + */ + ~SynchronizedTimeBaseConsumer() noexcept; + + /** + * @traceid{SWS_TS_01003} + * @threadsafety{re-entrant} + * @param[in] stbc The SynchronizedTimeBaseConsumer object to be moved. + */ + SynchronizedTimeBaseConsumer(SynchronizedTimeBaseConsumer&& stbc) noexcept; + + /** + * @traceid{SWS_TS_01004} + * @threadsafety{re-entrant} + * @param[in] stbc The SynchronizedTimeBaseConsumer object to be moved. + * @returns A reference to this object. + */ + SynchronizedTimeBaseConsumer& operator=(SynchronizedTimeBaseConsumer&& stbc) & noexcept; + + /** + * @traceid{SWS_TS_01005} + */ + SynchronizedTimeBaseConsumer(const SynchronizedTimeBaseConsumer&) = delete; + + /** + * @traceid{SWS_TS_01006} + */ + SynchronizedTimeBaseConsumer& operator=(const SynchronizedTimeBaseConsumer&) = delete; + + /** + * @returns The current time of the synchronized clock. + * @traceid{SWS_TS_01007} + */ + Timestamp GetCurrentTime() const noexcept; + + /** + * @returns The current rate deviation. + * @traceid{SWS_TS_01008} + */ + double GetRateDeviation() const noexcept; + + /** + * @returns A clock specific TimeBaseStatus that contains all the relevant clock information. + * @traceid{SWS_TS_01009} + */ + SynchronizedTimeBaseStatus GetTimeWithStatus() const noexcept; + + /** + * @traceid{SWS_TS_01010} + */ + void RegisterStatusChangeNotifier(std::function notifier) noexcept; + + /** + * @traceid{SWS_TS_01011} + */ + void UnregisterStatusChangeNotifier() noexcept; + + /** + * @param[in] notifier The function to unregister. + * @traceid{SWS_TS_01012} + */ + void RegisterSynchronizationStateChangeNotifier( + std::function notifier) noexcept; + + /** + * @traceid{SWS_TS_01013} + */ + void UnregisterSynchronizationStateChangeNotifier() noexcept; + + /** + * @param[in] notifier The function to be called if the TimeBaseStatus has changed. + * @traceid{SWS_TS_01014} + */ + void RegisterTimeLeapNotifier(std::function notifier) noexcept; + + /** + * @traceid{SWS_TS_01015} + */ + void UnregisterTimeLeapNotifier() noexcept; + + /** + * @param[in] timeBaseProviderNotification time consumer application notification object that will be notified about + * the availability of a new data block recorded for the Time Base + * @traceid{SWS_TS_01016} + */ + void RegisterTimeValidationNotification( + ConsumerTimeBaseValidationNotification& timeBaseValidationNotification) noexcept; + + /** + * @traceid{SWS_TS_01017} + */ + void UnregisterTimeValidationNotification() noexcept; + + /** + * @param[in] notifier The function to be called. + * @traceid{SWS_TS_01018} + */ + void RegisterTimePrecisionMeasurementNotifier( + std::function notifier) noexcept; + + /** + * @traceid{SWS_TS_01019} + */ + void UnregisterTimePrecisionMeasurementNotifier() noexcept; + +private: + std::unique_ptr time_base_reader_; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYNCHRONIZED_TIME_BASE_CONSUMER_H_ diff --git a/src/tsync-lib/include/score/time/synchronized_time_base_provider.h b/src/tsync-lib/include/score/time/synchronized_time_base_provider.h new file mode 100644 index 0000000..6e462ad --- /dev/null +++ b/src/tsync-lib/include/score/time/synchronized_time_base_provider.h @@ -0,0 +1,144 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYNCHRONIZED_TIME_BASE_PROVIDER_H_ +#define SCORE_TIME_SYNCHRONIZED_TIME_BASE_PROVIDER_H_ + +#include +#include + +#include "score/span.hpp" +#include "score/result/result.h" + +#include "score/time/provider_time_base_validation_notification.h" +#include "score/time/timestamp.h" + +namespace score { +namespace time { + +class ITimeBaseWriter; +class ITimeBaseReader; +class TsyncNamedSemaphore; + +/** + * @traceid{SWS_TS_01100} + */ +class SynchronizedTimeBaseProvider final { +public: + /** + * @param instance_specifier String representation of an score::mw::com::InstanceSpecifier to + * a PortPrototype of a TimeSynchronizationInterface + * + * @traceid{SWS_TS_01101} + */ + explicit SynchronizedTimeBaseProvider(const std::string_view& instance_specifier) noexcept; + + /** + * @traceid{SWS_TS_01102} + * @threadsafety{re-entrant} + * @param[in] stbp The SynchronizedTimeBaseProvider object to be moved. + */ + SynchronizedTimeBaseProvider(SynchronizedTimeBaseProvider&& stbp) noexcept; + + /** + * @traceid{SWS_TS_01103} + * @threadsafety{re-entrant} + * @param[in] stbp The SynchronizedTimeBaseProvider object to be moved. + * @returns The moved SynchronizedTimeBaseProvider object. + */ + SynchronizedTimeBaseProvider& operator=(SynchronizedTimeBaseProvider&& stbp) noexcept; + + /** + * @traceid{SWS_TS_01104} + */ + SynchronizedTimeBaseProvider(const SynchronizedTimeBaseProvider&) = delete; + + /** + * @traceid{SWS_TS_01105} + */ + SynchronizedTimeBaseProvider& operator=(const SynchronizedTimeBaseProvider&) = delete; + + /** + * @traceid{SWS_TS_01106} + * @threadsafety{no} + */ + ~SynchronizedTimeBaseProvider() noexcept; + + /** + * @param[in] timePoint The time information to be set. + * @param[in] userData The user data to be set. + * @error TimeErrorCode::kTimeCannotSet the action cannot be executed, because the connection to time sync daemon is + * currently lost + * @traceid{SWS_TS_01107} + */ + score::ResultBlank SetTime(score::time::Timestamp timePoint, + score::cpp::span userData = {}) noexcept; + + /** + * @param[in] timePoint The time information to be set. + * @param[in] userData The user data to be set. + * @error TimeErrorCode::kDaemonConnectionLost the action cannot be executed, because the connection to time sync + * daemon is currently lost + * @tparam Duration The duration type of the time point passed as parameter. + * @traceid{SWS_TS_01108} + */ + score::ResultBlank UpdateTime(score::time::Timestamp timePoint, + score::cpp::span userData = {}) noexcept; + + /** + * @returns The current time as clock specific time point. + * @traceid{SWS_TS_01109} + */ + Timestamp GetCurrentTime() const noexcept; + + /** + * @param[in] rateCorrection The rate correction to be applied. 0.5 is two times slower, whilst 2.0 is 2 times + * faster. + * @error score::time::TimeErrorDomain::Errc::kLimitsExceeded + * @traceid{SWS_TS_01110} + */ + score::ResultBlank SetRateCorrection(double rateCorrection) noexcept; + + /** + * @returns The current rate deviation. + * @traceid{SWS_TS_01111} + * @uptrace{RS_TS_00018} + */ + double GetRateDeviation() const noexcept; + + /** + * @param[in] userData The user data to be set. + * @traceid{SWS_TS_01112} + */ + score::ResultBlank SetUserData(score::cpp::span userData) noexcept; + + /** + * @returns A vector of bytes holding the user data that was set during the creation of the status. + * @traceid{SWS_TS_01113} + */ + score::cpp::span GetUserData() const noexcept; + + /** + * @param[in] timeBaseProviderNotification time provider application notification object that will be notified about + * the availability of a new data block recorded for the Time Base + * @traceid{SWS_TS_01114} + */ + void RegisterTimeValidationNotification( + ProviderTimeBaseValidationNotification& timeBaseValidationNotification) noexcept; + + /** + * @traceid{SWS_TS_01115} + */ + void UnregisterTimeValidationNotification() noexcept; + +private: + std::unique_ptr time_base_writer_; + std::unique_ptr time_base_reader_; + std::unique_ptr transmission_sem_; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYNCHRONIZED_TIME_BASE_PROVIDER_H_ diff --git a/src/tsync-lib/include/score/time/synchronized_time_base_status.h b/src/tsync-lib/include/score/time/synchronized_time_base_status.h new file mode 100644 index 0000000..aef8658 --- /dev/null +++ b/src/tsync-lib/include/score/time/synchronized_time_base_status.h @@ -0,0 +1,100 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYNCHRONIZED_TIME_BASE_STATUS_H_ +#define SCORE_TIME_SYNCHRONIZED_TIME_BASE_STATUS_H_ + +#include + +#include "score/span.hpp" +#include "score/time/timestamp.h" + +namespace score { +namespace time { + +/** + * @traceid{SWS_TS_01050} + */ +enum class SynchronizationStatus : std::uint32_t { + kNotSynchronizedUntilStartup = 0, + kTimeOut = 1, + kSynchronized = 2, + kSynchToGateway = 3, +}; + +/** + * @traceid{SWS_TS_01051} + */ +enum class LeapJump : std::uint32_t { + kTimeLeapNone = 0, + kTimeLeapFuture = 1, + kTimeLeapPast = 2, +}; + +/** + * @traceid{SWS_TS_01052} + */ +class SynchronizedTimeBaseStatus final { +public: + /** + * @returns SynchronizationStatus + * @traceid{SWS_TS_01053} + */ + SynchronizationStatus GetSynchronizationStatus() const noexcept; + + /** + * @returns LeapJump + * @traceid{SWS_TS_01054} + */ + LeapJump GetLeapJump() const noexcept; + + /** + * @returns The point in time at which this object was created. Time point is expressed in context of the clock that + * created this object. + * @traceid{SWS_TS_01055} + */ + score::time::Timestamp GetCreationTime() const noexcept; + + /** + * @returns A vector of bytes holding the user data that was set during the creation of the status. + * A size of zero indicates that no user data is available. + * @traceid{SWS_TS_01056} + */ + score::cpp::span GetUserData() const noexcept; + + /** + * @traceid{SWS_TS_01057} + */ + SynchronizedTimeBaseStatus(SynchronizedTimeBaseStatus&&) noexcept = default; + /** + * @traceid{SWS_TS_01058} + */ + SynchronizedTimeBaseStatus(const SynchronizedTimeBaseStatus&) noexcept = default; + /** + * @traceid{SWS_TS_01059} + */ + SynchronizedTimeBaseStatus& operator=(SynchronizedTimeBaseStatus&&) noexcept = default; + /** + * @traceid{SWS_TS_01060} + */ + SynchronizedTimeBaseStatus& operator=(const SynchronizedTimeBaseStatus&) noexcept = default; + /** + * @brief Destructor + */ + ~SynchronizedTimeBaseStatus() = default; + +private: + friend class TimeBaseStatusAccessMediator; + SynchronizedTimeBaseStatus() noexcept; + + SynchronizationStatus sync_status_{}; + LeapJump leap_jump_{}; + Timestamp creation_time_{}; + score::cpp::span user_data_{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYNCHRONIZED_TIME_BASE_STATUS_H_ diff --git a/src/tsync-lib/include/score/time/time_error_domain.h b/src/tsync-lib/include/score/time/time_error_domain.h new file mode 100644 index 0000000..ca3bca4 --- /dev/null +++ b/src/tsync-lib/include/score/time/time_error_domain.h @@ -0,0 +1,61 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_ERROR_DOMAIN_H +#define SCORE_TIME_ERROR_DOMAIN_H + +#include "score/result/error.h" +#include "score/result/error_code.h" +#include "score/result/error_domain.h" + +namespace score { +namespace time { + +enum class TimeErrorCode : score::result::ErrorCode { + kDaemonConnectionLost = 1, + kTimeCannotSet = 2, + kLimitsExceeded = 3 +}; + +class TimeErrorDomain final : public score::result::ErrorDomain { +public: + /// @traceid{SWS_CORE_00231} + using Errc = TimeErrorCode; + + constexpr TimeErrorDomain() noexcept = default; + + /// @param errorCode the error code value + /// @returns the text message + std::string_view MessageFor(const score::result::ErrorCode& errorCode) const noexcept override { + Errc const code = static_cast(errorCode); + switch (code) { + case Errc::kDaemonConnectionLost: + return "Daemon connection lost"; + case Errc::kTimeCannotSet: + return "Time cannot be set"; + case Errc::kLimitsExceeded: + return "Value out of bounds"; + default: + return "Unknown time error code"; + } + } +}; + +namespace internal { + constexpr TimeErrorDomain g_timeErrorDomain; +} + +constexpr score::result::ErrorDomain const& GetTimeErrorDomain() noexcept { + return internal::g_timeErrorDomain; +} + +score::result::Error MakeError(const TimeErrorCode code, const std::string_view message = "") +{ + return {static_cast(code), GetTimeErrorDomain(), message}; +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_ERROR_DOMAIN_H diff --git a/src/tsync-lib/include/score/time/time_precision_measurement_type.h b/src/tsync-lib/include/score/time/time_precision_measurement_type.h new file mode 100644 index 0000000..12d7084 --- /dev/null +++ b/src/tsync-lib/include/score/time/time_precision_measurement_type.h @@ -0,0 +1,43 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_TIME_PRECISION_MEASUREMENT_TYPES_H_ +#define SCORE_TIME_TIME_PRECISION_MEASUREMENT_TYPES_H_ + +#include + +namespace score { +namespace time { + +/// @traceid{SWS_TS_01400} +struct TimePrecisionMeasurement final { + /// @traceid{SWS_TS_01401} + std::uint32_t glbSeconds{}; + + /// @traceid{SWS_TS_01402} + std::uint32_t glbNanoSeconds{}; + + /// @traceid{SWS_TS_01403} + std::uint8_t timeBaseStatus{}; + + /// @traceid{SWS_TS_01404} + std::uint32_t virtualLocalTimeLow{}; + + /// @traceid{SWS_TS_01405} + std::int16_t rateDeviation{}; + + /// @traceid{SWS_TS_01406} + std::uint32_t locSeconds{}; + + /// @traceid{SWS_TS_01407} + std::uint32_t locNanoSeconds{}; + + /// @traceid{SWS_TS_01408} + std::uint32_t pathDelay{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_TIME_PRECISION_MEASUREMENT_TYPES_H_ diff --git a/src/tsync-lib/include/score/time/time_validation_measurement_types.h b/src/tsync-lib/include/score/time/time_validation_measurement_types.h new file mode 100644 index 0000000..2bf8aa5 --- /dev/null +++ b/src/tsync-lib/include/score/time/time_validation_measurement_types.h @@ -0,0 +1,99 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_TIME_VALIDATION_MEASUREMENT_TYPES_H_ +#define SCORE_TIME_TIME_VALIDATION_MEASUREMENT_TYPES_H_ + +#include + +#include "score/time/timestamp.h" + +namespace score { +namespace time { + +/// @traceid{SWS_TS_00414} +struct TimeMasterMeasurementType final { + /// @traceid{SWS_TS_14140} + score::time::Timestamp preciseOriginTimestamp{}; + + /// @traceid{SWS_TS_14141} + std::uint64_t syncEgressTimestamp{}; + + /// @traceid{SWS_TS_14142} + std::uint16_t sequenceId{}; +}; + +/// @traceid{SWS_TS_00415} +struct TimeSlaveMeasurementType final { + /// @traceid{SWS_TS_14150} + score::time::Timestamp preciseOriginTimestamp{}; + + /// @traceid{SWS_TS_14151} + score::time::Timestamp referenceGlobalTimestamp{}; + + /// @traceid{SWS_TS_14152} + std::uint64_t syncIngressTimestamp{}; + + /// @traceid{SWS_TS_14153} + std::int64_t correctionField{}; + + /// @traceid{SWS_TS_14154} + std::uint64_t referenceLocalTimestamp{}; + + /// @traceid{SWS_TS_14155} + std::uint32_t pDelay{}; + + /// @traceid{SWS_TS_14156} + std::uint16_t sequenceId{}; +}; + +/// @traceid{SWS_TS_00416} +struct PdelayInitiatorMeasurementType final { + /// @traceid{SWS_TS_14160} + std::uint64_t requestOriginTimestamp{}; + + /// @traceid{SWS_TS_14161} + std::uint64_t responseReceiptTimestamp{}; + + /// @traceid{SWS_TS_14162} + score::time::Timestamp requestReceiptTimestamp{}; + + /// @traceid{SWS_TS_14163} + score::time::Timestamp responseOriginTimestamp{}; + + /// @traceid{SWS_TS_14164} + std::uint64_t referenceLocalTimestamp{}; + + /// @traceid{SWS_TS_14165} + score::time::Timestamp referenceGlobalTimestamp{}; + + /// @traceid{SWS_TS_14166} + std::uint32_t pDelay{}; + + /// @traceid{SWS_TS_14167} + std::uint16_t sequenceId{}; +}; + +/// @traceid{SWS_TS_00417} +struct PdelayResponderMeasurementType final { + /// @traceid{SWS_TS_14170} + std::uint64_t requestReceiptTimestamp{}; + + /// @traceid{SWS_TS_14171} + std::uint64_t responseOriginTimestamp{}; + + /// @traceid{SWS_TS_14172} + std::uint64_t referenceLocalTimestamp{}; + + /// @traceid{SWS_TS_14173} + score::time::Timestamp referenceGlobalTimestamp{}; + + /// @traceid{SWS_TS_14174} + std::uint16_t sequenceId{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_TIME_VALIDATION_MEASUREMENT_TYPES_H_ diff --git a/src/tsync-lib/include/score/time/timestamp.h b/src/tsync-lib/include/score/time/timestamp.h new file mode 100644 index 0000000..553c2bd --- /dev/null +++ b/src/tsync-lib/include/score/time/timestamp.h @@ -0,0 +1,34 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_TIMESTAMP_H_ +#define SCORE_TIME_TIMESTAMP_H_ + +#include +#include + +namespace score { +namespace time { + +/// @traceid{SWS_TS_01260} +struct TimeBase { + /// @traceid{SWS_TS_01261} + using rep = std::int64_t; + /// @traceid{SWS_TS_01262} + using period = std::nano; + /// @traceid{SWS_TS_01263} + using duration = std::chrono::duration; + /// @traceid{SWS_TS_01264} + using time_point = std::chrono::time_point; + /// @traceid{SWS_TS_01265} + constexpr static bool is_steady = false; +}; + +/// @traceid{SWS_TS_01251} +using Timestamp = std::chrono::time_point; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_TIMESTAMP_H_ diff --git a/src/tsync-lib/src/SynchronizedTimeBaseCommon.cpp b/src/tsync-lib/src/SynchronizedTimeBaseCommon.cpp new file mode 100644 index 0000000..d35f79a --- /dev/null +++ b/src/tsync-lib/src/SynchronizedTimeBaseCommon.cpp @@ -0,0 +1,112 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SynchronizedTimeBaseCommon.h" + +#include + +#include "score/time/utility/ITimeBaseReader.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncSharedUtils.h" + +using namespace std::chrono; + +namespace score { +namespace time { + +// clang-format off +// RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "This object will not be accessed during global static initialization.", true) +TsyncIdMappingsHandler mappings_handler{}; +// clang-format on + +uint32_t SynchronizedTimeBaseCommon::GetTimeBaseDomainId(std::string_view timebase_name) { + auto domain_name = timebase_name; + + if (domain_name.front() == '/') { + domain_name.remove_prefix(1u); + } + + auto id = mappings_handler.GetDomainId(domain_name); + return *id; +} + +std::optional SynchronizedTimeBaseCommon::GetCurrentTime(ITimeBaseReader& reader) { + reader.GetAccessor().Open(); + TimestampWithStatus ts; + VirtualLocalTime lt; + { + std::lock_guard lock(reader); + if (reader.Read(ts) && reader.Read(lt)) { + return GetInterpolatedTimestamp(ts, lt); + } + } + + return std::nullopt; +} + +std::optional SynchronizedTimeBaseCommon::GetInterpolatedTimestamp(const TimestampWithStatus& ts, + VirtualLocalTime ts_vlt) { + auto vlt{TsyncSharedUtils::GetCurrentVirtualLocalTime()}; + if (!vlt) { + return std::nullopt; + } + + auto ts_ns = ts.nanoseconds + duration_cast(ts.seconds); + VirtualLocalTime ns = ts_ns + std::chrono::nanoseconds{(*vlt).count() - ts_vlt.count()}; + auto d = duration_cast(ns); + return Timestamp(d); +} + +TimestampWithStatus SynchronizedTimeBaseCommon::GetTimestampFromNs(std::chrono::nanoseconds v) { + TimestampWithStatus ts; + auto s = duration_cast(v); + // coverity[autosar_cpp14_a4_7_1_violation] Because the implementation is designed not to overflow + std::chrono::nanoseconds ns_remainder{v.count() - duration_cast(s).count()}; + ts.status = SynchronizationStatus::kNotSynchronizedUntilStartup; + ts.seconds = s; + ts.nanoseconds = ns_remainder; + + return ts; +} + +std::optional SynchronizedTimeBaseCommon::GetUserData(ITimeBaseReader& reader) { + reader.GetAccessor().Open(); + std::lock_guard lock(reader); + UserDataView ud; + + if (AlignedSkip(reader) && + AlignedSkip(reader)) { + if (reader.Read(ud)) { + return ud; + } + } + + return std::nullopt; +} + +UserDataView SynchronizedTimeBaseCommon::SanitizeUserData(const score::cpp::span& ud) { + constexpr std::size_t kMaxUserDataSize = 3u; + if (ud.size() > kMaxUserDataSize) { + return (ud.first(kMaxUserDataSize)); + } + + return ud; +} + +std::optional SynchronizedTimeBaseCommon::GetSynchronizationStatus( + ITimeBaseReader& reader) { + reader.GetAccessor().Open(); + SynchronizationStatus status{}; + { + std::lock_guard lock(reader); + if (reader.Read(status)) { + return status; + } + } + + return std::nullopt; +} + +} // namespace time +} // namespace score diff --git a/src/tsync-lib/src/SynchronizedTimeBaseCommon.h b/src/tsync-lib/src/SynchronizedTimeBaseCommon.h new file mode 100644 index 0000000..b13c2f7 --- /dev/null +++ b/src/tsync-lib/src/SynchronizedTimeBaseCommon.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYNCHRONIZEDTIMEBASECOMMON_H_ +#define SCORE_TIME_SYNCHRONIZEDTIMEBASECOMMON_H_ + +#include +#include +#include +#include + +#include "score/span.hpp" + +#include "score/time/utility/ITimeBaseAccessor.h" + +#include "score/time/synchronized_time_base_status.h" +#include "score/time/timestamp.h" + +namespace score { +namespace time { + +class ITimeBaseReader; + +class SynchronizedTimeBaseCommon final { +public: + // static helpers for provider and consumer + static uint32_t GetTimeBaseDomainId(std::string_view timebase_name); + static std::optional GetCurrentTime(ITimeBaseReader& reader); + static std::optional GetUserData(ITimeBaseReader& reader); + static TimestampWithStatus GetTimestampFromNs(std::chrono::nanoseconds v); + static UserDataView SanitizeUserData(const score::cpp::span& ud); + static std::optional GetSynchronizationStatus(ITimeBaseReader& reader); + +private: + SynchronizedTimeBaseCommon(); + static std::optional GetInterpolatedTimestamp(const TimestampWithStatus& ts, + VirtualLocalTime ts_vlt); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYNCHRONIZEDTIMEBASECOMMON_H_ diff --git a/src/tsync-lib/src/SynchronizedTimeBaseConsumerImpl.cpp b/src/tsync-lib/src/SynchronizedTimeBaseConsumerImpl.cpp new file mode 100644 index 0000000..6fe5c5c --- /dev/null +++ b/src/tsync-lib/src/SynchronizedTimeBaseConsumerImpl.cpp @@ -0,0 +1,146 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/synchronized_time_base_consumer.h" + +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/ITimeBaseReader.h" +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncSharedUtils.h" + +#include "SynchronizedTimeBaseCommon.h" +#include "TimeBaseStatusAccessMediator.h" + +using namespace std::chrono; +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { + +extern TsyncIdMappingsHandler mappings_handler; + +SynchronizedTimeBaseConsumer::SynchronizedTimeBaseConsumer(const std::string_view& instance_specifier) noexcept { + auto domain_name = mappings_handler.GetDomainNameForConsumer(instance_specifier); + + if (domain_name) { + time_base_reader_ = TimeBaseReaderFactory::Create(*domain_name); + } else { + std::stringstream ss; + ss << "SynchronizedTimeBaseConsumer ctor: couldn't find time domain for consumer '" << instance_specifier + << "'"; + logFatalAndAbort(ss.str().c_str()); + } +} + +SynchronizedTimeBaseConsumer::~SynchronizedTimeBaseConsumer() noexcept = default; +SynchronizedTimeBaseConsumer::SynchronizedTimeBaseConsumer(SynchronizedTimeBaseConsumer&& stbc) noexcept = default; +SynchronizedTimeBaseConsumer& SynchronizedTimeBaseConsumer::operator=(SynchronizedTimeBaseConsumer&& stbc) & noexcept = default; + +Timestamp SynchronizedTimeBaseConsumer::GetCurrentTime() const noexcept { + auto sync_status = SynchronizedTimeBaseCommon::GetSynchronizationStatus(*time_base_reader_); + if (!sync_status || *sync_status == SynchronizationStatus::kNotSynchronizedUntilStartup) { + auto vlt = TsyncSharedUtils::GetCurrentVirtualLocalTime(); + if (!vlt) { + return Timestamp(Timestamp::duration::zero()); + } else { + return Timestamp(*vlt); + } + } + + auto ct = SynchronizedTimeBaseCommon::GetCurrentTime(*time_base_reader_); + if (ct) { + return *ct; + } + + return Timestamp(Timestamp::duration::zero()); +} + +SynchronizedTimeBaseStatus SynchronizedTimeBaseConsumer::GetTimeWithStatus() const noexcept { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + + auto sync_status{SynchronizedTimeBaseCommon::GetSynchronizationStatus(*time_base_reader_)}; + Timestamp ts_now{}; + + if (!sync_status.has_value() || *sync_status == SynchronizationStatus::kNotSynchronizedUntilStartup) { + sync_status = SynchronizationStatus::kNotSynchronizedUntilStartup; + auto rs = TsyncSharedUtils::GetCurrentVirtualLocalTime(); + if (!rs) { + ts_now = Timestamp(Timestamp::duration::zero()); + } else { + ts_now = Timestamp(*rs); + } + } else { + auto ct = SynchronizedTimeBaseCommon::GetCurrentTime(*time_base_reader_); + if (ct.has_value()) { + ts_now = *ct; + } + } + + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusCreationTime(status, ts_now); + // Get user data from shared memory + auto ud{SynchronizedTimeBaseCommon::GetUserData(*time_base_reader_)}; + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusUserData(status, ud.has_value() ? *ud : UserDataView()); + + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusSynchronizationStatus(status, *sync_status); + + return status; +} + +double SynchronizedTimeBaseConsumer::GetRateDeviation() const noexcept { + return 0.0; +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::RegisterStatusChangeNotifier( + std::function /* notifier */) noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::UnregisterStatusChangeNotifier() noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::RegisterSynchronizationStateChangeNotifier( + std::function /* notifier */) noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::UnregisterSynchronizationStateChangeNotifier() noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::RegisterTimeLeapNotifier( + std::function /* notifier */) noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::UnregisterTimeLeapNotifier() noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::RegisterTimeValidationNotification( + ConsumerTimeBaseValidationNotification& /* timeBaseValidationNotification */) noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::UnregisterTimeValidationNotification() noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::RegisterTimePrecisionMeasurementNotifier( + std::function /* notifier */) noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseConsumer::UnregisterTimePrecisionMeasurementNotifier() noexcept { +} + +} // namespace time +} // namespace score diff --git a/src/tsync-lib/src/SynchronizedTimeBaseProviderImpl.cpp b/src/tsync-lib/src/SynchronizedTimeBaseProviderImpl.cpp new file mode 100644 index 0000000..62b8fe7 --- /dev/null +++ b/src/tsync-lib/src/SynchronizedTimeBaseProviderImpl.cpp @@ -0,0 +1,138 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/synchronized_time_base_provider.h" + +#include +#include +#include + +#include "score/result/result.h" + +#include "score/time/common/Abort.h" + +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TimeBaseWriterFactory.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncNamedSemaphore.h" +#include "score/time/utility/TsyncSharedUtils.h" + +#include "score/time/time_error_domain.h" +#include "SynchronizedTimeBaseCommon.h" + +using score::time::common::logFatalAndAbort; +using score::cpp::span; + +namespace score { +namespace time { + +extern TsyncIdMappingsHandler mappings_handler; + +SynchronizedTimeBaseProvider::SynchronizedTimeBaseProvider(const std::string_view& instance_specifier) noexcept + : time_base_writer_{nullptr}, time_base_reader_{nullptr}, transmission_sem_{nullptr} { + auto domain_name = mappings_handler.GetDomainNameForProvider(instance_specifier); + if (domain_name) { + time_base_reader_ = TimeBaseReaderFactory::Create(*domain_name); + } else { + std::stringstream ss; + ss << "SynchronizedTimeBaseProvider ctor: couldn't find time domain for provider '" << instance_specifier + << "'"; + logFatalAndAbort(ss.str().c_str()); + } + + time_base_writer_ = TimeBaseWriterFactory::Create(*domain_name); + + // coverity[autosar_cpp14_a4_7_1_violation] Because the implementation is designed to not cause data loss here + std::uint32_t time_domain_id{SynchronizedTimeBaseCommon::GetTimeBaseDomainId(*domain_name)}; + transmission_sem_ = + std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(time_domain_id), + TsyncNamedSemaphore::OpenMode::Unsignaled, false); +} + +SynchronizedTimeBaseProvider::~SynchronizedTimeBaseProvider() noexcept = default; +SynchronizedTimeBaseProvider::SynchronizedTimeBaseProvider(SynchronizedTimeBaseProvider&& stbc) noexcept = default; +SynchronizedTimeBaseProvider& SynchronizedTimeBaseProvider::operator=(SynchronizedTimeBaseProvider&& stbp) noexcept = default; + +score::ResultBlank SynchronizedTimeBaseProvider::SetTime(score::time::Timestamp timePoint, + span userData) noexcept { + score::ResultBlank result = UpdateTime(timePoint, userData); + + if (result) { + transmission_sem_->unlock(); + } + + return result; +} + +score::ResultBlank SynchronizedTimeBaseProvider::UpdateTime(score::time::Timestamp timePoint, + span userData) noexcept { + UserDataView sanitized_ud = SynchronizedTimeBaseCommon::SanitizeUserData(userData); + + auto ts = SynchronizedTimeBaseCommon::GetTimestampFromNs(timePoint.time_since_epoch()); + ts.status = SynchronizationStatus::kSynchronized; + auto rt = TsyncSharedUtils::GetCurrentVirtualLocalTime(); + + if (rt) { + time_base_writer_->GetAccessor().Open(); + std::lock_guard lock(*time_base_writer_); + if (time_base_writer_->Write(ts) && time_base_writer_->Write(*rt) && time_base_writer_->Write(sanitized_ud)) { + return {}; + } + } + + return score::MakeUnexpected(score::time::TimeErrorCode::kDaemonConnectionLost); +} + +score::time::Timestamp SynchronizedTimeBaseProvider::GetCurrentTime() const noexcept { + auto ct{SynchronizedTimeBaseCommon::GetCurrentTime(*time_base_reader_)}; + if (ct) { + return *ct; + } + + return Timestamp(Timestamp::duration::zero()); +} + +score::ResultBlank SynchronizedTimeBaseProvider::SetRateCorrection(double /* rateCorrection */) noexcept { + return {}; +} + +double SynchronizedTimeBaseProvider::GetRateDeviation() const noexcept { + return 0.0; +} + +score::ResultBlank SynchronizedTimeBaseProvider::SetUserData(span userData) noexcept { + time_base_writer_->GetAccessor().Open(); + { + std::lock_guard l(*time_base_writer_); + + if (AlignedSkip(*time_base_writer_) && + AlignedSkip(*time_base_writer_) && time_base_writer_->Write(userData)) { + return {}; + } + } + + return score::MakeUnexpected(score::time::TimeErrorCode::kDaemonConnectionLost); +} + +span SynchronizedTimeBaseProvider::GetUserData() const noexcept { + auto ud{SynchronizedTimeBaseCommon::GetUserData(*time_base_reader_)}; + + if (ud.has_value()) { + return *ud; + } + + return {}; +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseProvider::RegisterTimeValidationNotification( + ProviderTimeBaseValidationNotification&) noexcept { +} + +// coverity[autosar_cpp14_m0_1_8_violation] This function is yet to be implemented. +void SynchronizedTimeBaseProvider::UnregisterTimeValidationNotification() noexcept { +} + +} // namespace time +} // namespace score diff --git a/src/tsync-lib/src/SynchronizedTimeBaseStatusImpl.cpp b/src/tsync-lib/src/SynchronizedTimeBaseStatusImpl.cpp new file mode 100644 index 0000000..b056cd7 --- /dev/null +++ b/src/tsync-lib/src/SynchronizedTimeBaseStatusImpl.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/synchronized_time_base_status.h" + +namespace score { +namespace time { + +SynchronizedTimeBaseStatus::SynchronizedTimeBaseStatus() noexcept + : sync_status_(SynchronizationStatus::kNotSynchronizedUntilStartup), leap_jump_(LeapJump::kTimeLeapNone) { +} + +SynchronizationStatus SynchronizedTimeBaseStatus::GetSynchronizationStatus() const noexcept { + return sync_status_; +} + +LeapJump SynchronizedTimeBaseStatus::GetLeapJump() const noexcept { + return leap_jump_; +} + +score::time::Timestamp SynchronizedTimeBaseStatus::GetCreationTime() const noexcept { + return creation_time_; +} + +score::cpp::span SynchronizedTimeBaseStatus::GetUserData() const noexcept { + return user_data_; +} + +} // namespace time +} // namespace score diff --git a/src/tsync-lib/src/TimeBaseStatusAccessMediator.h b/src/tsync-lib/src/TimeBaseStatusAccessMediator.h new file mode 100644 index 0000000..b7c8a21 --- /dev/null +++ b/src/tsync-lib/src/TimeBaseStatusAccessMediator.h @@ -0,0 +1,45 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_TIMEBASESTATUSACCESMEDIATOR_H_ +#define SCORE_TIME_TIMEBASESTATUSACCESMEDIATOR_H_ + +#include "score/time/synchronized_time_base_status.h" +#include "score/time/utility/TsyncSharedUtils.h" + +namespace score { +namespace time { + +class TimeBaseStatusAccessMediator { +public: + static SynchronizedTimeBaseStatus CreateSynchronizedTimeBaseStatusInstance() noexcept { + return SynchronizedTimeBaseStatus(); + } + + static void SetSynchronizedTimeBaseStatusCreationTime(SynchronizedTimeBaseStatus& status, + const Timestamp& creationTime) noexcept { + status.creation_time_ = creationTime; + } + + static void SetSynchronizedTimeBaseStatusUserData(SynchronizedTimeBaseStatus& status, + UserDataView userData) noexcept { + status.user_data_ = userData; + } + + static void SetSynchronizedTimeBaseStatusLeapJump(SynchronizedTimeBaseStatus& status, LeapJump leapJump) noexcept { + status.leap_jump_ = leapJump; + } + + static void SetSynchronizedTimeBaseStatusSynchronizationStatus(SynchronizedTimeBaseStatus& status, + SynchronizationStatus syncStatus) noexcept { + status.sync_status_ = syncStatus; + } + + TimeBaseStatusAccessMediator() = delete; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_TIMEBASESTATUSACCESMEDIATOR_H_ diff --git a/src/tsync-lib/test/SynchronizedTimeBaseConsumer_UT/BUILD b/src/tsync-lib/test/SynchronizedTimeBaseConsumer_UT/BUILD new file mode 100644 index 0000000..c79df35 --- /dev/null +++ b/src/tsync-lib/test/SynchronizedTimeBaseConsumer_UT/BUILD @@ -0,0 +1,23 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "SynchronizedTimeBaseConsumer_UT", + srcs = [ + "SynchronizedTimeBaseConsumer_UT.cpp", + ], + deps = [ + "//src/tsync-lib:tsync_if", + "//src/tsync-lib:synchronized_time_base_common", + "//src/tsync-lib:synchronized_time_base_consumer_impl", + "//src/tsync-lib:synchronized_time_base_status_impl", + "//src/tsync-lib/test/matchers:test_matchers", + "//src/tsync-lib/test/mocks:mocks", + "//src/tsync-utility-lib:utility_tsync_id_mappings_handler", + "//src/tsync-utility-lib:utility_tsync_named_semaphore", + "//src/tsync-utility-lib:utility_tsync_read_write_lock", + "//src/tsync-utility-lib:utility_sys_calls", + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/mocks:utility_tsync_shared_utils_mock", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-lib/test/SynchronizedTimeBaseConsumer_UT/SynchronizedTimeBaseConsumer_UT.cpp b/src/tsync-lib/test/SynchronizedTimeBaseConsumer_UT/SynchronizedTimeBaseConsumer_UT.cpp new file mode 100644 index 0000000..4f6f00d --- /dev/null +++ b/src/tsync-lib/test/SynchronizedTimeBaseConsumer_UT/SynchronizedTimeBaseConsumer_UT.cpp @@ -0,0 +1,656 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +//#include "tsync-ptp-lib/tsync_ptp_lib_types.h" +#include "matcher_operators.h" +#include "ConsumerTimeBaseValidationNotificationMock.h" +#include "SharedMemTimeBaseReaderMock.h" +#include "SharedMemTimeBaseWriterMock.h" +#include "TimeBaseReaderFactoryMock.h" +#include "TimeBaseWriterFactoryMock.h" +#include "TsyncSharedUtilsMock.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncSharedUtils.h" + +#define private public +// class under test +#include "SynchronizedTimeBaseCommon.h" +#include "score/time/synchronized_time_base_consumer.h" +#undef private + +namespace score { +namespace time { +// defined in SynchronizedTimeBaseCommon.cpp +extern TsyncIdMappingsHandler mappings_handler; +} // namespace time +} // namespace score + +using InstanceSpecifier = std::string_view; +using score::time::reader_factory_mock; +using score::time::shared_utils_mock; +using score::time::writer_factory_mock; +using namespace score::time; +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::ReturnRef; + +class SynchronizedTimeBaseConsumerFixture : public ::testing::Test { +protected: + void SetUp() override { + reader_factory_mock = std::make_unique<::testing::NiceMock>(); + writer_factory_mock = std::make_unique<::testing::NiceMock>(); + shared_utils_mock = std::make_unique<::testing::NiceMock>(); + mappings_handler.Clear(); + ASSERT_TRUE(mappings_handler.AddDomainMapping(1U, "one")); + ASSERT_TRUE(mappings_handler.AddDomainMapping(2U, "two")); + ASSERT_TRUE(mappings_handler.AddConsumerToDomain(1U, "consumer1")); + ASSERT_TRUE(mappings_handler.AddConsumerToDomain("two", "consumer2")); + + reader_factory_mock_return_real_reader = true; + writer_factory_mock_return_real_writer = true; + mappings_handler.CommitMappingsToSharedMemory(); + mappings_handler.DoSharedMemoryRead(); + ASSERT_FALSE(mappings_handler.IsEmpty()); + reader_factory_mock_return_real_reader = false; + writer_factory_mock_return_real_writer = false; + + // install abort handler for our death tests + std::signal(SIGABRT, &AbortHandler); + // As we use singleton mock objecty, clear expectations after each test + ::testing::Mock::AllowLeak(reader_factory_mock.get()); + ::testing::Mock::AllowLeak(writer_factory_mock.get()); + ::testing::Mock::AllowLeak(shared_utils_mock.get()); + } + + void TearDown() override { + CleanUp(); + std::signal(SIGABRT, SIG_DFL); + } + + static void CleanUp() { + reader_factory_mock.reset(); + writer_factory_mock.reset(); + shared_utils_mock.reset(); + } + + static void AbortHandler(int /*signal*/) noexcept { + CleanUp(); + std::exit(EXIT_CODE); + } + + static const int32_t EXIT_CODE; +}; + +const int32_t SynchronizedTimeBaseConsumerFixture::EXIT_CODE = 1; + +using SynchronizedTimeBaseCommonFixture = SynchronizedTimeBaseConsumerFixture; + +namespace testing { +namespace synchronizedtimebaseconsumer_ut { + +TEST_F(SynchronizedTimeBaseConsumerFixture, Ctor_Succeeds) { + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + // defaults are checked + ASSERT_NE(stbc.time_base_reader_.get(), nullptr); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, Ctor_OnInvalidConsumerName_Aborts) { + ASSERT_EXIT({ SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("invalid")); }, + ::testing::ExitedWithCode(EXIT_CODE), "couldn't find time domain for consumer"); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, Dtor_Succeeds) { + SynchronizedTimeBaseConsumer *stbc = new SynchronizedTimeBaseConsumer(InstanceSpecifier("consumer2")); + delete stbc; +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, MoveCtor_Succeeds) { + SynchronizedTimeBaseConsumer stbc1(InstanceSpecifier("consumer1")); + // for later comparision + auto timeBaseReaderPtrComp = stbc1.time_base_reader_.get(); + SynchronizedTimeBaseConsumer stbc2(std::move(stbc1)); + + ASSERT_EQ(stbc2.time_base_reader_.get(), timeBaseReaderPtrComp); + ASSERT_EQ(stbc1.time_base_reader_, nullptr); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, MoveAssignment_Succeeds) { + SynchronizedTimeBaseConsumer stbc1(InstanceSpecifier("consumer1")); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + SynchronizedTimeBaseConsumer stbc2(InstanceSpecifier("consumer2")); + // stb1 != stc2 + ASSERT_NE(stbc1.time_base_reader_.get(), stbc2.time_base_reader_.get()); + + // for later comparision + auto timeBaseReaderPtrComp = stbc1.time_base_reader_.get(); + + // values are moved form stbc1 to stbc2 + stbc2 = std::move(stbc1); + + // stb2 == stc1 after move + ASSERT_EQ(stbc2.time_base_reader_.get(), timeBaseReaderPtrComp); + + ASSERT_EQ(stbc1.time_base_reader_, nullptr); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetCurrentTime_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + score::time::TimestampWithStatus ts = {SynchronizationStatus::kNotSynchronizedUntilStartup, + std::chrono::nanoseconds(0), std::chrono::seconds(43)}; + const uint32_t ns_diff{100000U}; + VirtualLocalTime lt(ns_diff); + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Open()).Times(2); + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillRepeatedly(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())).WillRepeatedly(DoAll(SetArgReferee<0>(lt), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kSynchronized), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + + // return real reader for handling of timebase mappings + // reader_factory_mock_return_real_reader = true; + uint64_t ns_sum = (std::chrono::duration_cast(ts.seconds) + ts.nanoseconds).count(); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(ns_sum))); + auto ts1 = stbc.GetCurrentTime(); + + ns_sum += static_cast(ns_diff); + + EXPECT_CALL(*reader_mock, Open()).Times(2); + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(lt), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kSynchronized), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(ns_sum))); + + auto ts2 = stbc.GetCurrentTime(); + + std::chrono::nanoseconds nsHelper(ns_diff); + + auto d_cmp = + std::chrono::duration_cast(ts1.time_since_epoch() + std::chrono::nanoseconds(ns_diff)); + + ASSERT_LE(d_cmp.count(), ts2.time_since_epoch().count()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetCurrentTime_WithMultipleConsumer_Succeeds) { + // 1. Create consumers object + std::string_view shmem_name("/one"); + std::string_view shmem_name2("/two"); + SynchronizedTimeBaseConsumer *stbc = new SynchronizedTimeBaseConsumer(InstanceSpecifier("consumer1")); + SynchronizedTimeBaseConsumer *stbc2 = new SynchronizedTimeBaseConsumer(InstanceSpecifier("consumer2")); + // 2. Set Up for function GetCurrentTime for consumer1 + auto reader_mock = static_cast(stbc->time_base_reader_.get()); + score::time::TimestampWithStatus ts = {SynchronizationStatus::kNotSynchronizedUntilStartup, + std::chrono::nanoseconds(0), std::chrono::seconds(43)}; + const uint32_t ns_diff{100000U}; + VirtualLocalTime lt(ns_diff); + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Open()).Times(2); + EXPECT_CALL(*reader_mock, lock()).Times(2); + EXPECT_CALL(*reader_mock, Read(An())) + .WillRepeatedly(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kSynchronized), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())).WillRepeatedly(DoAll(SetArgReferee<0>(lt), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(2); + + uint64_t ns_sum = (std::chrono::duration_cast(ts.seconds) + ts.nanoseconds).count(); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(ns_sum))); + auto ts_read = stbc->GetCurrentTime(); + + // 3. Set Up for function GetCurrentTime for consumer2 + auto reader_mock2 = static_cast(stbc2->time_base_reader_.get()); + score::time::TimestampWithStatus ts2 = {SynchronizationStatus::kNotSynchronizedUntilStartup, + std::chrono::nanoseconds(12345), std::chrono::seconds(43)}; + VirtualLocalTime lt2(ns_diff); + + EXPECT_CALL(*reader_mock2, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock2)); + EXPECT_CALL(*reader_mock2, GetName()).WillRepeatedly(Return(shmem_name2)); + EXPECT_CALL(*reader_mock2, Open()).Times(2); + EXPECT_CALL(*reader_mock2, lock()).Times(2); + EXPECT_CALL(*reader_mock2, Read(An())) + .WillRepeatedly(DoAll(SetArgReferee<0>(ts2), Return(true))); + EXPECT_CALL(*reader_mock2, Read(An())) + .WillRepeatedly(DoAll(SetArgReferee<0>(lt2), Return(true))); + EXPECT_CALL(*reader_mock2, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kSynchronized), Return(true))); + EXPECT_CALL(*reader_mock2, unlock()).Times(2); + + uint64_t ns_sum2 = (std::chrono::duration_cast(ts2.seconds) + ts2.nanoseconds).count(); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(ns_sum2))); + auto ts_read2 = stbc2->GetCurrentTime(); + // 4. Make sure that there no Segmentation fault + delete (stbc); + delete (stbc2); + EXPECT_LE(ts_read.time_since_epoch(), ts_read2.time_since_epoch()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetCurrentTime_OnReaderFailInvalidVlt_SucceedsWithZeroTimestamp) { + std::string_view shmem_name("/two"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer2")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(Return(false)); + + auto ts1 = stbc.GetCurrentTime(); + + ASSERT_EQ(Timestamp::duration::zero(), ts1.time_since_epoch()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetCurrentTime_WithStatuskNotSynchronizedUntilStartup_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + score::time::TimestampWithStatus ts = {SynchronizationStatus::kNotSynchronizedUntilStartup, + std::chrono::nanoseconds(0), std::chrono::seconds(43)}; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Open()).Times(1); + EXPECT_CALL(*reader_mock, lock()).Times(1); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kNotSynchronizedUntilStartup), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(1); + + // return real reader for handling of timebase mappings + // reader_factory_mock_return_real_reader = true; + uint64_t ns_sum = (std::chrono::duration_cast(ts.seconds) + ts.nanoseconds).count(); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(ns_sum))); + auto ts1 = stbc.GetCurrentTime(); + EXPECT_EQ(ts1.time_since_epoch(), std::chrono::nanoseconds(ns_sum)); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetCurrentTime_WithGetCurrentVirtualLocalTimeFailed_Failed) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Open()).Times(1); + EXPECT_CALL(*reader_mock, lock()).Times(1); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(SynchronizationStatus::kNotSynchronizedUntilStartup), Return(true))); + EXPECT_CALL(*reader_mock, unlock()).Times(1); + + // return real reader for handling of timebase mappings + // reader_factory_mock_return_real_reader = true; + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::nullopt)); + auto ts1 = stbc.GetCurrentTime(); + EXPECT_EQ(ts1.time_since_epoch(), Timestamp::duration::zero()); +} + +// TODO: The following 4 tests could be converted to a parametrized test +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + auto ud_arr = make_array(1, 2, 3); + score::cpp::span ud_span = ud_arr; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*reader_mock, Read(An &>())) + .WillOnce(DoAll(SetArgReferee<0>(ud_span), Return(true))); + + auto status = stbc.GetTimeWithStatus(); + auto ud = status.GetUserData(); + + ASSERT_EQ(ud, ud_span); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_WithEmptyUserData_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + score::cpp::span ud_span; + score::cpp::span empty_span; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*reader_mock, Read(empty_span)).WillOnce(DoAll(SetArgReferee<0>(ud_span), Return(true))); + + auto status = stbc.GetTimeWithStatus(); + auto ud = status.GetUserData(); + + ASSERT_TRUE(ud.empty()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_WithOneByteUserData_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + auto ud_arr = make_array(1); + score::cpp::span ud_span = ud_arr; + score::cpp::span empty_span; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*reader_mock, Read(empty_span)).WillOnce(DoAll(SetArgReferee<0>(ud_span), Return(true))); + + auto status = stbc.GetTimeWithStatus(); + auto ud = status.GetUserData(); + + ASSERT_EQ(ud, ud_span); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_WithTwoByteUserData_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + auto ud_arr = make_array(1, 2); + score::cpp::span ud_span = ud_arr; + score::cpp::span empty_span; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*reader_mock, Read(empty_span)).WillOnce(DoAll(SetArgReferee<0>(ud_span), Return(true))); + + auto status = stbc.GetTimeWithStatus(); + auto ud = status.GetUserData(); + + ASSERT_EQ(ud, ud_span); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnNotSynchronized_SucceedsWithCorrectStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // None of the status bits set + SynchronizationStatus status_to_inject = SynchronizationStatus::kNotSynchronizedUntilStartup; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(1000))); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kNotSynchronizedUntilStartup, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnSynchronizedWithCurrentTime_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // None of the status bits set + SynchronizationStatus status_to_inject = SynchronizationStatus::kSynchronized; + TimestampWithStatus ts{}; + VirtualLocalTime lt{}; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(lt), Return(true))); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(VirtualLocalTime{})); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kSynchronized, status.GetSynchronizationStatus()); + ASSERT_EQ(status.GetCreationTime().time_since_epoch().count(), 0u); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, + GetTimeWithStatus_OnSynchronizedWithNoCurrentTime_SucceedsWithCorrectStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + SynchronizationStatus status_to_inject = SynchronizationStatus::kSynchronized; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(Return(false)); + + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kSynchronized, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, + GetTimeWithStatus_WithGetCurrentVirtualLocalTimeFailed_SucceedsWithCorrectStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // None of the status bits set + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())).WillOnce(Return(false)); + EXPECT_CALL(*shared_utils_mock, GetCurrentVirtualLocalTime()).WillOnce(Return(std::chrono::nanoseconds(1000))); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kNotSynchronizedUntilStartup, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnTimeout_SucceedsWithCorrectStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // Mock Timeout + SynchronizationStatus status_to_inject = SynchronizationStatus::kTimeOut; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kTimeOut, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnNotSynchronizedToGateway_SucceedsWithCorrectStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // Mock SynctoGateway + SynchronizationStatus status_to_inject = SynchronizationStatus::kSynchToGateway; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kSynchToGateway, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnSynchronized_SucceedsWithCorrectStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // Mock GlobalSync + SynchronizationStatus status_to_inject = SynchronizationStatus::kSynchronized; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(true))); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kSynchronized, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnReadStatusFailure_FailWithInvalidStatus) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + // Mock GlobalSync + SynchronizationStatus status_to_inject = SynchronizationStatus::kNotSynchronizedUntilStartup; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(status_to_inject), Return(false))); + auto status = stbc.GetTimeWithStatus(); + + ASSERT_EQ(SynchronizationStatus::kNotSynchronizedUntilStartup, status.GetSynchronizationStatus()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetTimeWithStatus_OnEmptyUserData_Succeeds) { + std::string_view shmem_name("/one"); + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + score::cpp::span empty_span; + + EXPECT_CALL(*reader_mock, GetAccessor()).WillRepeatedly(ReturnRef(*reader_mock)); + EXPECT_CALL(*reader_mock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*reader_mock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*reader_mock, Read(empty_span)).WillOnce(Return(false)); + + auto status = stbc.GetTimeWithStatus(); + auto ud = status.GetUserData(); + + ASSERT_EQ(ud, empty_span); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, GetRateDeviation_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act + auto result = stbc.GetRateDeviation(); + + // Assert + ASSERT_EQ(result, 0.0); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, RegisterStatusChangeNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.RegisterStatusChangeNotifier(nullptr)); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, UnregisterStatusChangeNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.UnregisterStatusChangeNotifier()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, RegisterSynchronizationStateChangeNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.RegisterSynchronizationStateChangeNotifier(nullptr)); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, UnregisterSynchronizationStateChangeNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.UnregisterSynchronizationStateChangeNotifier()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, RegisterTimeLeapNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.RegisterTimeLeapNotifier(nullptr)); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, UnregisterTimeLeapNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.UnregisterTimeLeapNotifier()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, RegisterTimeValidationNotification_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + ConsumerTimeBaseValidationNotificationMock notification; + + // Act and assert + EXPECT_NO_THROW(stbc.RegisterTimeValidationNotification(notification)); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, UnregisterTimeValidationNotification_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.UnregisterTimeValidationNotification()); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, RegisterTimePrecisionMeasurementNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.RegisterTimePrecisionMeasurementNotifier(nullptr)); +} + +TEST_F(SynchronizedTimeBaseConsumerFixture, UnregisterTimePrecisionMeasurementNotifier_Succeeds) { + // Arrange + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + + // Act and assert + EXPECT_NO_THROW(stbc.UnregisterTimePrecisionMeasurementNotifier()); +} + +TEST_F(SynchronizedTimeBaseCommonFixture, GetUserData_OnAlignmentFailure_Fails) { + SynchronizedTimeBaseConsumer stbc(InstanceSpecifier("consumer1")); + // defaults are checked + ASSERT_NE(stbc.time_base_reader_.get(), nullptr); + + auto reader_mock = static_cast(stbc.time_base_reader_.get()); + + EXPECT_CALL(*reader_mock, GetPosition()).WillRepeatedly(testing::Return(4097)); + auto res = SynchronizedTimeBaseCommon::GetUserData(*reader_mock); + + ASSERT_FALSE(res.has_value()); +} + +} // namespace synchronizedtimebaseconsumer_ut +} // namespace testing diff --git a/src/tsync-lib/test/SynchronizedTimeBaseProvider_UT/BUILD b/src/tsync-lib/test/SynchronizedTimeBaseProvider_UT/BUILD new file mode 100644 index 0000000..b14b4f7 --- /dev/null +++ b/src/tsync-lib/test/SynchronizedTimeBaseProvider_UT/BUILD @@ -0,0 +1,21 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "SynchronizedTimeBaseProvider_UT", + srcs = [ + "SynchronizedTimeBaseProvider_UT.cpp", + ], + deps = [ + "//src/tsync-lib:tsync_if", + "//src/tsync-lib:synchronized_time_base_common", + "//src/tsync-lib:synchronized_time_base_provider_impl", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/tsync-utility-lib:utility_tsync_id_mappings_handler", + "//src/tsync-utility-lib:utility_tsync_named_semaphore", + "//src/tsync-utility-lib:utility_tsync_read_write_lock", + "//src/tsync-utility-lib:utility_sys_calls", + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/mocks:utility_tsync_shared_utils_mock", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-lib/test/SynchronizedTimeBaseProvider_UT/SynchronizedTimeBaseProvider_UT.cpp b/src/tsync-lib/test/SynchronizedTimeBaseProvider_UT/SynchronizedTimeBaseProvider_UT.cpp new file mode 100644 index 0000000..aef5c7c --- /dev/null +++ b/src/tsync-lib/test/SynchronizedTimeBaseProvider_UT/SynchronizedTimeBaseProvider_UT.cpp @@ -0,0 +1,698 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include +#include +#include + +#include "SynchronizedTimeBaseCommon.h" +#include "score/time/time_error_domain.h" +#include "matcher_operators.h" +#include "SharedMemTimeBaseReaderMock.h" +#include "SharedMemTimeBaseWriterMock.h" +#include "TimeBaseReaderFactoryMock.h" +#include "TimeBaseWriterFactoryMock.h" +#include "TsyncSharedUtilsMock.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncNamedSemaphore.h" +#include "score/time/utility/TsyncSharedUtils.h" + +#define private public +// class under test +#include "score/time/synchronized_time_base_provider.h" +#undef private + +using InstanceSpecifier = std::string_view; + +using namespace score::time; +using ::testing::_; +using ::testing::An; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::ReturnRef; + +namespace score { +namespace time { +// defined in SynchronizedTimeBaseCommon.cpp +extern TsyncIdMappingsHandler mappings_handler; +} // namespace time +} // namespace score + +using score::time::mappings_handler; + +namespace score { +namespace cpp { +bool operator==(const score::cpp::span& v1, const score::cpp::span& v2) { + return (std::equal(std::begin(v1), std::end(v1), std::begin(v2), std::end(v2))); +} + +bool operator!=(const score::cpp::span& v1, const score::cpp::span& v2) { + return (!(v1 == v2)); +} +} // namespace cpp +} // namespace score + +class SynchronizedTimeBaseProviderFixture : public ::testing::Test { +public: + void SetUp() override { + shared_utils_mock = std::make_unique<::testing::NiceMock>(); + reader_factory_mock = std::make_unique<::testing::NiceMock>(); + writer_factory_mock = std::make_unique<::testing::NiceMock>(); + mappings_handler.Clear(); + ASSERT_TRUE(mappings_handler.AddDomainMapping(1U, "one")); + ASSERT_TRUE(mappings_handler.AddDomainMapping(2U, "two")); + ASSERT_TRUE(mappings_handler.AddProviderToDomain(1U, "provider1")); + ASSERT_TRUE(mappings_handler.AddProviderToDomain("two", "provider2")); + reader_factory_mock_return_real_reader = true; + writer_factory_mock_return_real_writer = true; + mappings_handler.CommitMappingsToSharedMemory(); + mappings_handler.DoSharedMemoryRead(); + ASSERT_FALSE(mappings_handler.IsEmpty()); + reader_factory_mock_return_real_reader = false; + writer_factory_mock_return_real_writer = false; + + std::signal(SIGABRT, &AbortHandler); + ::testing::Mock::AllowLeak(shared_utils_mock.get()); + ::testing::Mock::AllowLeak(reader_factory_mock.get()); + ::testing::Mock::AllowLeak(writer_factory_mock.get()); + } + + void TearDown() override { + CleanUp(); + std::signal(SIGABRT, SIG_DFL); + } + + static void CleanUp() { + shared_utils_mock.reset(); + reader_factory_mock.reset(); + writer_factory_mock.reset(); + } + + static void AbortHandler(int /*signal*/) noexcept { + CleanUp(); + std::exit(EXIT_CODE); + } + + static const int32_t EXIT_CODE; +}; + +const int32_t SynchronizedTimeBaseProviderFixture::EXIT_CODE = 1; + +// This fixture is for testing the setting of user data of different lengths. It will be +// parametrized with values 0..4 +class SynchronizedTimeBaseProviderSetUserDataFixture : public ::testing::TestWithParam { +public: + void SetUp() override { + shared_utils_mock = std::make_unique<::testing::NiceMock>(); + reader_factory_mock = std::make_unique<::testing::NiceMock>(); + writer_factory_mock = std::make_unique<::testing::NiceMock>(); + mappings_handler.Clear(); + ASSERT_TRUE(mappings_handler.AddDomainMapping(1U, "one")); + ASSERT_TRUE(mappings_handler.AddDomainMapping(2U, "two")); + ASSERT_TRUE(mappings_handler.AddProviderToDomain(1U, "provider1")); + ASSERT_TRUE(mappings_handler.AddProviderToDomain("two", "provider2")); + reader_factory_mock_return_real_reader = true; + writer_factory_mock_return_real_writer = true; + mappings_handler.CommitMappingsToSharedMemory(); + mappings_handler.DoSharedMemoryRead(); + reader_factory_mock_return_real_reader = false; + writer_factory_mock_return_real_writer = false; + } + + void TearDown() override { + shared_utils_mock.reset(); + reader_factory_mock.reset(); + writer_factory_mock.reset(); + } +}; + +namespace testing { +namespace synchronizedtimebaseprovider_ut { + +TEST_F(SynchronizedTimeBaseProviderFixture, Ctor_Succeeds) { + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + ASSERT_NE(stbp.time_base_writer_.get(), nullptr); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, Ctor_OnInvalidProviderName_Aborts) { + ASSERT_EXIT({ SynchronizedTimeBaseProvider stbp(::InstanceSpecifier("invalid")); }, + ::testing::ExitedWithCode(EXIT_CODE), "couldn't find time domain for provider"); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, GetTimeBaseDomainId_Succeeds) { + auto domain_id = score::time::SynchronizedTimeBaseCommon::GetTimeBaseDomainId("/one"); + EXPECT_EQ(1U, domain_id); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, MoveCtor_Succeeds) { + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp1(InstanceSpecifier("provider1")); + auto writer = stbp1.time_base_writer_.get(); + auto reader = stbp1.time_base_reader_.get(); + ASSERT_NE(writer, nullptr); + ASSERT_NE(reader, nullptr); + SynchronizedTimeBaseProvider stbp2(std::move(stbp1)); + ASSERT_EQ(stbp1.time_base_writer_, nullptr); + ASSERT_EQ(stbp1.time_base_reader_, nullptr); + ASSERT_EQ(writer, stbp2.time_base_writer_.get()); + ASSERT_EQ(reader, stbp2.time_base_reader_.get()); + dummy_sem1.reset(); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, MoveAssignment_Succeeds) { + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp1(InstanceSpecifier("provider1")); + auto writer = stbp1.time_base_writer_.get(); + auto reader = stbp1.time_base_reader_.get(); + ASSERT_NE(writer, nullptr); + ASSERT_NE(reader, nullptr); + std::unique_ptr dummy_sem2; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_2"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp2(InstanceSpecifier("provider2")); + ASSERT_NE(writer, stbp2.time_base_writer_.get()); + ASSERT_NE(reader, stbp2.time_base_reader_.get()); + stbp2 = std::move(stbp1); + ASSERT_EQ(stbp1.time_base_writer_, nullptr); + ASSERT_EQ(stbp1.time_base_reader_, nullptr); + ASSERT_EQ(writer, stbp2.time_base_writer_.get()); + ASSERT_EQ(reader, stbp2.time_base_reader_.get()); + dummy_sem1.reset(); + dummy_sem2.reset(); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, GetCurrentTime_Succeeds) { + std::string_view shmem_name("/one"); + // Set up for constructor + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + + auto readerMock = static_cast(stbp.time_base_reader_.get()); + TimestampWithStatus ts = {SynchronizationStatus::kNotSynchronizedUntilStartup, std::chrono::nanoseconds(0), + std::chrono::seconds(43)}; + std::chrono::nanoseconds ns_inject = + ts.nanoseconds + std::chrono::duration_cast(ts.seconds); + const uint32_t ns_diff{100000U}; + VirtualLocalTime lt(ns_diff); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(lt), Return(true))); + EXPECT_CALL(*readerMock, unlock()).Times(1); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(ns_inject)); + + // return real reader for handling of timebase mappings + auto ts1 = stbp.GetCurrentTime(); + reader_factory_mock_return_real_reader = false; + ; + + auto ns_sum = ns_inject + std::chrono::nanoseconds(ns_diff); + + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(lt), Return(true))); + EXPECT_CALL(*readerMock, unlock()).Times(1); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(ns_sum)); + + auto ts2 = stbp.GetCurrentTime(); + + std::chrono::nanoseconds nsHelper(ns_diff); + + auto d_cmp = + std::chrono::duration_cast(ts1.time_since_epoch() + std::chrono::nanoseconds(ns_diff)); + + ASSERT_EQ(d_cmp.count(), ts2.time_since_epoch().count()); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, GetCurrentTime_OnReaderFail_ReturnsZeroTime) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto readerMock = static_cast(stbp.time_base_reader_.get()); + TimestampWithStatus ts = {}; + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(ts), Return(true))); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(Return(false)); + EXPECT_CALL(*readerMock, unlock()).Times(1); + + // return real reader for handling of timebase mappings + reader_factory_mock_return_real_reader = true; + auto ts1 = stbp.GetCurrentTime(); + reader_factory_mock_return_real_reader = false; + ; + + ASSERT_EQ(Timestamp::duration::zero(), ts1.time_since_epoch()); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, GetCurrentTime_OnTimeStampReadFailure_ReturnsZeroTime) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbc(InstanceSpecifier("provider1")); + auto readerMock = static_cast(stbc.time_base_reader_.get()); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(Return(false)); + auto ts1 = stbc.GetCurrentTime(); + ASSERT_EQ(Timestamp::duration::zero(), ts1.time_since_epoch()); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, UpdateTime_Succeeds) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + uint32_t ns_diff = 100000; + VirtualLocalTime vlt_to_inject(ns_diff); + + std::chrono::nanoseconds ts_ns(56789); + Timestamp ts{ts_ns}; + auto tst = SynchronizedTimeBaseCommon::GetTimestampFromNs(ts_ns); + tst.status = SynchronizationStatus::kSynchronized; + auto span_data = make_array(3, 1, 2, 3); + score::cpp::span byte_span(span_data); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, Open()).Times(1); + EXPECT_CALL(*writerMock, lock()).Times(1); + EXPECT_CALL(*writerMock, Write(Matcher(tst))).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, Write(Matcher(vlt_to_inject))).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, Write(Matcher>(_))).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, unlock()).Times(1); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject)); + + auto res = stbp.UpdateTime(ts, byte_span); + ASSERT_TRUE(res); + + // a bit of a sanity check here to verify symmetry of UpdateTime and GetCurrentTime + auto readerMock = static_cast(stbp.time_base_reader_.get()); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(tst), Return(true))); + EXPECT_CALL(*readerMock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(vlt_to_inject), Return(true))); + EXPECT_CALL(*readerMock, unlock()).Times(1); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject)); + + auto ts_read = stbp.GetCurrentTime(); + + ASSERT_EQ(ts.time_since_epoch(), ts_read.time_since_epoch()); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, UpdateTime_WhenCalledTwice_Succeeds) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + uint32_t ns_diff = 100000; + VirtualLocalTime vlt_to_inject(ns_diff); + + std::chrono::nanoseconds ts_ns(56789); + Timestamp ts{ts_ns}; + auto tst = SynchronizedTimeBaseCommon::GetTimestampFromNs(ts_ns); + tst.status = SynchronizationStatus::kSynchronized; + auto span_data = make_array(3, 1, 2, 3); + score::cpp::span byte_span(span_data); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillRepeatedly(Return(vlt_to_inject)); + EXPECT_CALL(*writerMock, Open()).Times(2); + EXPECT_CALL(*writerMock, lock()).Times(2); + EXPECT_CALL(*writerMock, Write(Matcher(tst))).WillRepeatedly(Return(true)); + EXPECT_CALL(*writerMock, Write(Matcher(vlt_to_inject))).WillRepeatedly(Return(true)); + EXPECT_CALL(*writerMock, Write(Matcher(_))).WillRepeatedly(Return(true)); + EXPECT_CALL(*writerMock, unlock()).Times(2); + + auto res = stbp.UpdateTime(ts, byte_span); + ASSERT_TRUE(res); + + res = stbp.UpdateTime(ts, byte_span); + ASSERT_TRUE(res); + + // a bit of a sanity check here to verify symmetry of UpdateTime and GetCurrentTime + auto readerMock = static_cast(stbp.time_base_reader_.get()); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(DoAll(SetArgReferee<0>(tst), Return(true))); + EXPECT_CALL(*readerMock, Read(An())) + .WillOnce(DoAll(SetArgReferee<0>(vlt_to_inject), Return(true))); + EXPECT_CALL(*readerMock, unlock()).Times(1); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject)); + + auto ts_read = stbp.GetCurrentTime(); + + ASSERT_EQ(ts.time_since_epoch(), ts_read.time_since_epoch()); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, UpdateTime_OnGetVltError_ReturnsError) { + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + // EXPECT_CALL(*writerMock, GetName()).WillOnce(Return("/one")); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(std::nullopt)); + + Timestamp ts{std::chrono::nanoseconds(56789)}; + auto res = stbp.UpdateTime(ts); + ASSERT_FALSE(res); + ASSERT_EQ(res.error(), TimeErrorCode::kDaemonConnectionLost); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, UpdateTime_OnVltReadError_ReturnsError) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + TimestampWithStatus tst = {}; + tst.status = SynchronizationStatus::kSynchronized; + VirtualLocalTime vlt_to_inject(0); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*writerMock, Open()).Times(1); + EXPECT_CALL(*writerMock, lock()).Times(1); + EXPECT_CALL(*writerMock, Write(Matcher(tst))).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, Write(Matcher(vlt_to_inject))).WillOnce(Return(false)); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject)); + EXPECT_CALL(*writerMock, unlock()).Times(1); + + Timestamp ts{std::chrono::nanoseconds(0)}; + + auto res = stbp.UpdateTime(ts); + ASSERT_FALSE(res); + ASSERT_EQ(res.error(), TimeErrorCode::kDaemonConnectionLost); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, GetUserData_OnError_ReturnsEmptyUserData) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto readerMock = static_cast(stbp.time_base_reader_.get()); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Read(An())).WillOnce(Return(false)); + + auto res = stbp.GetUserData(); + ASSERT_EQ(res.size(), 0u); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, SetTime_Succeeds) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + uint32_t ns_diff = 100000; + VirtualLocalTime vlt_to_inject(ns_diff); + + std::chrono::nanoseconds ts_ns(56789); + Timestamp ts{ts_ns}; + auto tst = SynchronizedTimeBaseCommon::GetTimestampFromNs(ts_ns); + tst.status = SynchronizationStatus::kSynchronized; + + auto span_data = make_array(3, 1, 2, 3); + score::cpp::span byte_span(span_data); + + // expected calls from successful UpdateTime call + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*writerMock, Open()).Times(1); + EXPECT_CALL(*writerMock, lock()).Times(1); + EXPECT_CALL(*writerMock, Write(Matcher(tst))).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, Write(Matcher(vlt_to_inject))).WillOnce(Return(true)); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(vlt_to_inject)); + EXPECT_CALL(*writerMock, Write(Matcher(_))).WillOnce(Return(true)); + + EXPECT_CALL(*writerMock, unlock()).Times(1); + + auto res = stbp.SetTime(ts, byte_span); + ASSERT_TRUE(res); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, SetUserData_OnWriterSetPositionFailure_ReturnsError) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + EXPECT_CALL(*writerMock, GetAccessor()).WillOnce(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*writerMock, GetPosition()).WillRepeatedly(::Return(43u)); // unaligned condition, forcing SetPosition + EXPECT_CALL(*writerMock, SetPosition(_)).WillRepeatedly(::Return(false)); + + score::cpp::span user_data; + + auto res = stbp.SetUserData(user_data); + ASSERT_FALSE(res); + ASSERT_EQ(res.error(), TimeErrorCode::kDaemonConnectionLost); +} + +TEST_F(SynchronizedTimeBaseProviderFixture, SetTime_OnUpdateTimeHaveNoValue_ReturnsError) { + std::string_view shmem_name("one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + std::chrono::nanoseconds ts_ns(56789); + Timestamp ts{ts_ns}; + auto span_data = make_array(3, 1, 2, 3); + score::cpp::span byte_span(span_data); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return("one")); + EXPECT_CALL(*shared_utils_mock.get(), GetCurrentVirtualLocalTime()).WillOnce(Return(std::nullopt)); + + auto res = stbp.SetTime(ts, byte_span); + ASSERT_FALSE(res); +} + +} // namespace lib_synchronizedtimebaseprovider_ut + +class ProviderTimeBaseValidationNotificationImpl final : public ProviderTimeBaseValidationNotification { +public: + void SetPdelayResponderData(const PdelayResponderMeasurementType&) override { + } + + void SetMasterTimingData(const TimeMasterMeasurementType&) override { + } +}; + +namespace lib_synchronizedtimebaseprovider_ut { + +TEST_F(SynchronizedTimeBaseProviderFixture, RegisterTimeValidationNotification_Succeeds) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + ProviderTimeBaseValidationNotificationImpl notification; + stbp.RegisterTimeValidationNotification(notification); + stbp.UnregisterTimeValidationNotification(); + SUCCEED(); +} + +TEST_P(SynchronizedTimeBaseProviderSetUserDataFixture, SetGetUserData_Succeeds) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + auto ud_len = GetParam(); + auto span_data = new std::byte[ud_len]; + + for (int i = 0; i < ud_len; ++i) { + span_data[i] = std::byte(i + 1); + } + + score::cpp::span byte_span(span_data, ud_len); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*writerMock, Open()).Times(1); + EXPECT_CALL(*writerMock, lock()).Times(1); + EXPECT_CALL(*writerMock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*writerMock, Write(An())).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, unlock()).Times(1); + + auto res = stbp.SetUserData(byte_span); + ASSERT_TRUE(res); + + auto readerMock = static_cast(stbp.time_base_reader_.get()); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*readerMock, Read(An&>())).WillOnce(Return(true)); + EXPECT_CALL(*readerMock, unlock()).Times(1); + + stbp.GetUserData(); + + delete[] span_data; +} + +} // namespace lib_synchronizedtimebaseprovider_ut + +INSTANTIATE_TEST_SUITE_P(SynchronizedTimeBaseProviderUserDataTests, SynchronizedTimeBaseProviderSetUserDataFixture, + testing::Values(0, 1, 2, 3, 4)); + +namespace lib_synchronizedtimebaseprovider_ut { + +TEST_F(SynchronizedTimeBaseProviderFixture, SetGetUserData_Succeeds) { + std::string_view shmem_name("/one"); + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + auto writerMock = static_cast(stbp.time_base_writer_.get()); + + auto span_data = make_array(3, 1, 2, 3); + score::cpp::span byte_span(span_data); + + EXPECT_CALL(*writerMock, GetAccessor()).WillRepeatedly(ReturnRef(*writerMock)); + EXPECT_CALL(*writerMock, GetName()).WillRepeatedly(Return(shmem_name)); + EXPECT_CALL(*writerMock, Open()).Times(1); + EXPECT_CALL(*writerMock, lock()).Times(1); + EXPECT_CALL(*writerMock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*writerMock, Write(An())).WillOnce(Return(true)); + EXPECT_CALL(*writerMock, unlock()).Times(1); + + auto res = stbp.SetUserData(byte_span); + ASSERT_TRUE(res); + + auto readerMock = static_cast(stbp.time_base_reader_.get()); + + EXPECT_CALL(*readerMock, GetAccessor()).WillRepeatedly(ReturnRef(*readerMock)); + EXPECT_CALL(*readerMock, Open()).Times(1); + EXPECT_CALL(*readerMock, lock()).Times(1); + EXPECT_CALL(*readerMock, SetPosition(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*readerMock, Read(An&>())) + .WillOnce(DoAll(SetArgReferee<0>(byte_span), Return(true))); + EXPECT_CALL(*readerMock, unlock()).Times(1); + + auto read_ud = stbp.GetUserData(); + + ASSERT_EQ(byte_span, read_ud); +} + +// // TODO: implement this test alongside with the GetRateDeviation. +TEST_F(SynchronizedTimeBaseProviderFixture, GetRateDeviation_Succeeds) { + // Arrange + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + + // Act + auto result = stbp.GetRateDeviation(); + + // Assert + ASSERT_EQ(result, 0.0); +} + +// // TODO: implement this test alongside with the SetRateCorrection. +TEST_F(SynchronizedTimeBaseProviderFixture, SetRateCorrection_Succeeds) { + // Arrange + std::unique_ptr dummy_sem1; + EXPECT_CALL(*shared_utils_mock.get(), GetTransmissionSemaphoreName(_)) + .WillRepeatedly(Return(std::string("time_domain_1"))); + dummy_sem1 = std::make_unique(TsyncSharedUtils::GetTransmissionSemaphoreName(1), + TsyncNamedSemaphore::OpenMode::Unsignaled, true); + SynchronizedTimeBaseProvider stbp(InstanceSpecifier("provider1")); + + // Act + auto result = stbp.SetRateCorrection(1); + + // Assert + ASSERT_TRUE(result); +} + +} // namespace synchronizedtimebaseprovider_ut +} // namespace testing diff --git a/src/tsync-lib/test/SynchronizedTimeBaseStatus_UT/BUILD b/src/tsync-lib/test/SynchronizedTimeBaseStatus_UT/BUILD new file mode 100644 index 0000000..b228a99 --- /dev/null +++ b/src/tsync-lib/test/SynchronizedTimeBaseStatus_UT/BUILD @@ -0,0 +1,13 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "SynchronizedTimeBaseStatus_UT", + srcs = [ + "SynchronizedTimeBaseStatus_UT.cpp", + ], + deps = [ + "//src/tsync-lib:tsync_if", + "//src/tsync-lib:synchronized_time_base_status_impl", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-lib/test/SynchronizedTimeBaseStatus_UT/SynchronizedTimeBaseStatus_UT.cpp b/src/tsync-lib/test/SynchronizedTimeBaseStatus_UT/SynchronizedTimeBaseStatus_UT.cpp new file mode 100644 index 0000000..937f1cd --- /dev/null +++ b/src/tsync-lib/test/SynchronizedTimeBaseStatus_UT/SynchronizedTimeBaseStatus_UT.cpp @@ -0,0 +1,250 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +/// @brief SynchronizedTimeBaseStatus Unit/Binary Tests +/// +/// @page SynchronizedTimeBaseStatusUnitBinaryTests +/// @{ +/// @verbatim embed:rst:leading-slashes +/// +/// .. test_spec:: SynchronizedTimeBaseStatusTest +/// :id: TS_UT_TS_BT_SYNCTIMEBASESTATUS +/// :status: accepted +/// :satisfied_by: UT_BT_TESTFILE_SYNCTIMEBASESTATUS +/// :tests: SWS_TS_01052, SWS_TS_01053, SWS_TS_01054, SWS_TS_01055, SWS_TS_01056, SWS_TS_01057, SWS_TS_01058, +/// SWS_TS_01059, SWS_TS_01060, PUB_TSYNC_TIMEBASESTATUS_INTERFACE, SB_TSYNC_LIB +/// :tags: test_req_based, test_interface +/// +/// **Test Scope:** +/// +/// Verify the existence of the SynchronizedTimeBaseStatus class and the behavior of its methods. +/// +/// **Test Design:** +/// +/// Create an instance of the SynchronizedTimeBaseStatus class, and validate its APIs by using +/// the GoogleTest Framework. +/// +/// **Test Cases:** +/// +/// .. doxygengroup:: SyncTimeBaseStatus_Test_Cases +/// :project: tsync +/// +/// @endverbatim +/// @} + +#include +#include + +#include "score/time/utility/TsyncSharedUtils.h" + +// class under test +#include "score/time/synchronized_time_base_status.h" +#include "TimeBaseStatusAccessMediator.h" + +using namespace score::time; + +const SynchronizationStatus sync_status = SynchronizationStatus::kSynchronized; +const LeapJump leap_jump = LeapJump::kTimeLeapFuture; +auto ud_arr = make_array(1, 2, 3); +const score::cpp::span ud_span = ud_arr; + +namespace testing { +namespace synchronizedtimebasestatus_ut { + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the existence of the SynchronizedTimeBaseStatus class via default construction. +/// +/// **Tests:** :need:`SWS_TS_01052` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, ExistenceDefaultCtorDtor) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + + ASSERT_EQ(status.GetSynchronizationStatus(), SynchronizationStatus::kNotSynchronizedUntilStartup); + ASSERT_EQ(status.GetLeapJump(), LeapJump::kTimeLeapNone); + ASSERT_EQ(status.GetCreationTime().time_since_epoch(), Timestamp::duration::zero()); + ASSERT_TRUE(status.GetUserData().empty()); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the SynchronizedTimeBaseStatus class move constructor. +/// +/// **Tests:** :need:`SWS_TS_01057` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, MoveCtor) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + + Timestamp time_stamp(std::chrono::system_clock::now().time_since_epoch()); + + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusSynchronizationStatus(status, sync_status); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusLeapJump(status, leap_jump); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusCreationTime(status, time_stamp); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusUserData(status, ud_span); + + SynchronizedTimeBaseStatus moved_status(std::move(status)); + ASSERT_EQ(moved_status.GetSynchronizationStatus(), sync_status); + ASSERT_EQ(moved_status.GetLeapJump(), leap_jump); + ASSERT_EQ(moved_status.GetCreationTime(), time_stamp); + ASSERT_TRUE(std::equal(moved_status.GetUserData().begin(), moved_status.GetUserData().end(), ud_span.begin(), + ud_span.end())); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the SynchronizedTimeBaseStatus class copy constructor. +/// +/// **Tests:** :need:`SWS_TS_01058` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, CopyCtor) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + + Timestamp time_stamp(std::chrono::system_clock::now().time_since_epoch()); + + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusSynchronizationStatus(status, sync_status); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusLeapJump(status, leap_jump); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusCreationTime(status, time_stamp); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusUserData(status, ud_span); + + SynchronizedTimeBaseStatus copied_status(status); + ASSERT_EQ(copied_status.GetSynchronizationStatus(), sync_status); + ASSERT_EQ(copied_status.GetLeapJump(), leap_jump); + ASSERT_EQ(copied_status.GetCreationTime(), time_stamp); + ASSERT_TRUE(std::equal(copied_status.GetUserData().begin(), copied_status.GetUserData().end(), ud_span.begin(), + ud_span.end())); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the SynchronizedTimeBaseStatus class move assignment operator. +/// +/// **Tests:** :need:`SWS_TS_01059` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, MoveAssignment) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + + Timestamp time_stamp(std::chrono::system_clock::now().time_since_epoch()); + + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusSynchronizationStatus(status, sync_status); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusLeapJump(status, leap_jump); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusCreationTime(status, time_stamp); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusUserData(status, ud_span); + + SynchronizedTimeBaseStatus moved_status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + moved_status = std::move(status); + ASSERT_EQ(moved_status.GetSynchronizationStatus(), sync_status); + ASSERT_EQ(moved_status.GetLeapJump(), leap_jump); + ASSERT_EQ(moved_status.GetCreationTime(), time_stamp); + ASSERT_TRUE(std::equal(moved_status.GetUserData().begin(), moved_status.GetUserData().end(), ud_span.begin(), + ud_span.end())); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the SynchronizedTimeBaseStatus class copy assignment operator. +/// +/// **Tests:** :need:`SWS_TS_01060` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, CopyAssignment) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + + Timestamp time_stamp(std::chrono::system_clock::now().time_since_epoch()); + + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusSynchronizationStatus(status, sync_status); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusLeapJump(status, leap_jump); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusCreationTime(status, time_stamp); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusUserData(status, ud_span); + + SynchronizedTimeBaseStatus copied_status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + copied_status = status; + + ASSERT_EQ(copied_status.GetSynchronizationStatus(), sync_status); + ASSERT_EQ(copied_status.GetLeapJump(), leap_jump); + ASSERT_EQ(copied_status.GetCreationTime(), time_stamp); + ASSERT_TRUE(std::equal(copied_status.GetUserData().begin(), copied_status.GetUserData().end(), ud_span.begin(), + ud_span.end())); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the GetSynchronizationStatus() API. +/// +/// **Tests:** :need:`SWS_TS_01053` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, GetSynchronizationStatus) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusSynchronizationStatus(status, sync_status); + + ASSERT_EQ(status.GetSynchronizationStatus(), sync_status); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the GetLeapJump() API. +/// +/// **Tests:** :need:`SWS_TS_01054` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, GetLeapJump) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusLeapJump(status, leap_jump); + + ASSERT_EQ(status.GetLeapJump(), leap_jump); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the GetCreationTime() API. +/// +/// **Tests:** :need:`SWS_TS_01055` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, GetCreationTime) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + Timestamp time_stamp(std::chrono::system_clock::now().time_since_epoch()); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusCreationTime(status, time_stamp); + + ASSERT_EQ(status.GetCreationTime(), time_stamp); +} + +/// @addtogroup SyncTimeBaseStatus_Test_Cases +/// @verbatim embed:rst:leading-slashes +/// **Test Case:** Verify the behavior of the GetUserData() API. +/// +/// **Tests:** :need:`SWS_TS_01056` +/// +/// **Expected Result:** All assertions hold. +/// +/// @endverbatim +TEST(SynchronizedTimeBaseStatusTest, GetUserData) { + SynchronizedTimeBaseStatus status = TimeBaseStatusAccessMediator::CreateSynchronizedTimeBaseStatusInstance(); + TimeBaseStatusAccessMediator::SetSynchronizedTimeBaseStatusUserData(status, ud_span); + + ASSERT_TRUE(std::equal(status.GetUserData().begin(), status.GetUserData().end(), ud_span.begin(), ud_span.end())); +} + +} // namespace synchronizedtimebasestatus_ut +} // namespace testing diff --git a/src/tsync-lib/test/TimeErrorDomain_UT/BUILD b/src/tsync-lib/test/TimeErrorDomain_UT/BUILD new file mode 100644 index 0000000..e826359 --- /dev/null +++ b/src/tsync-lib/test/TimeErrorDomain_UT/BUILD @@ -0,0 +1,13 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TimeErrorDomain_UT", + srcs = [ + "TimeErrorDomain_UT.cpp", + ], + deps = [ + "@score_baselibs//score/result", + "//src/tsync-lib:tsync_if", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-lib/test/TimeErrorDomain_UT/TimeErrorDomain_UT.cpp b/src/tsync-lib/test/TimeErrorDomain_UT/TimeErrorDomain_UT.cpp new file mode 100644 index 0000000..c93502d --- /dev/null +++ b/src/tsync-lib/test/TimeErrorDomain_UT/TimeErrorDomain_UT.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include "score/time/time_error_domain.h" + +using namespace score::time; + +class TimeErrorDomainTestFixture : public ::testing::Test { +protected: + TimeErrorDomain time_error_domain_; +}; + +class TimeErrorDomainMessageTestFixture : public TimeErrorDomainTestFixture, + public ::testing::WithParamInterface> {}; + +// Considered test cases: +// kDaemonConnectionLost - returns message about lost connection. +// kTimeCannotSet - returns message that time can't be set. +// kLimitsExceeded - returns message that value out of bounds. +// Any error code which is not part of enum - returns that error code is unknown. +INSTANTIATE_TEST_SUITE_P(TimeErrorDomainMessageTests, TimeErrorDomainMessageTestFixture, + ::testing::Values(std::make_tuple(TimeErrorCode::kDaemonConnectionLost, "Daemon connection lost"), + std::make_tuple(TimeErrorCode::kTimeCannotSet, "Time cannot be set"), + std::make_tuple(TimeErrorCode::kLimitsExceeded, "Value out of bounds"), + std::make_tuple(static_cast(0xFF), "Unknown time error code"))); + +namespace testing { +namespace tsyncerrordomain_ut { + +// Test whether Message return correct string for error code. In case if error code is invalid, it would return that the +// error code is unknown. +TEST_P(TimeErrorDomainMessageTestFixture, MessageFor_DifferentErrorCodes_RetrunCorrectDescription) { + // Arrange + auto parameters = GetParam(); + auto& error_code = std::get<0>(parameters); + auto& expected_result = std::get<1>(parameters); + + // Act + auto message = time_error_domain_.MessageFor(static_cast(error_code)); + + // Assert + ASSERT_EQ(message, expected_result); +} + +TEST(TimeErrorDomainTest, GetTimeErrorDomain__NoExceptionOrCrashNorNullptr) { + // Act + const score::result::ErrorDomain& time_error_domain = GetTimeErrorDomain(); + + // Assert + EXPECT_NE(&time_error_domain, nullptr); +} + +TEST(TimeErrorDomainTest, MakeError__ErrorCreatedWithCorrectContents) { + // Act + score::result::Error error = MakeError(TimeErrorCode::kDaemonConnectionLost, "Test message"); + + // Assert + EXPECT_EQ(static_cast(TimeErrorCode::kDaemonConnectionLost), *error); + EXPECT_EQ("Daemon connection lost", error.Message()); + EXPECT_EQ("Test message", error.UserMessage()); +} + +} // namespace tsyncerrordomain_ut +} // namespace testing diff --git a/src/tsync-lib/test/Timestamp_UT/BUILD b/src/tsync-lib/test/Timestamp_UT/BUILD new file mode 100644 index 0000000..60f6477 --- /dev/null +++ b/src/tsync-lib/test/Timestamp_UT/BUILD @@ -0,0 +1,12 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "Timestamp_UT", + srcs = [ + "Timestamp_UT.cpp", + ], + deps = [ + "//src/tsync-lib:tsync_if", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-lib/test/Timestamp_UT/Timestamp_UT.cpp b/src/tsync-lib/test/Timestamp_UT/Timestamp_UT.cpp new file mode 100644 index 0000000..84a328a --- /dev/null +++ b/src/tsync-lib/test/Timestamp_UT/Timestamp_UT.cpp @@ -0,0 +1,20 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include + +using namespace score::time; + +namespace testing { +namespace timestampexistence_ut { + +TEST(TimeStamp_Existence_UT, TypeExists) { + EXPECT_TRUE(std::is_constructible::value); +} + +} // namespace timestampexistence_ut +} // namespace testing diff --git a/src/tsync-lib/test/matchers/BUILD b/src/tsync-lib/test/matchers/BUILD new file mode 100644 index 0000000..fc732d6 --- /dev/null +++ b/src/tsync-lib/test/matchers/BUILD @@ -0,0 +1,14 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +# load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_library( + name = "test_matchers", + hdrs = [ + "matcher_operators.h", + ], + strip_include_prefix = ".", + deps = [ + "@score_baselibs//score/language/futurecpp", + ], + visibility = ["//visibility:public"] +) diff --git a/src/tsync-lib/test/matchers/matcher_operators.h b/src/tsync-lib/test/matchers/matcher_operators.h new file mode 100644 index 0000000..b466d9c --- /dev/null +++ b/src/tsync-lib/test/matchers/matcher_operators.h @@ -0,0 +1,28 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_MATCHER_OPERATORS_H_ +#define SCORE_TIME_MATCHER_OPERATORS_H_ + + +#include +#include + +#include "score/span.hpp" + +namespace score { +namespace cpp { + +inline bool operator==(const span& v1, const span& v2) { + return (std::equal(std::begin(v1), std::end(v1), std::begin(v2), std::end(v2))); +} + +inline bool operator!=(const span& v1, const span& v2) { + return (!(v1 == v2)); +} + +} // namespace cpp +} // namespace score + +#endif // SCORE_TIME_MATCHER_OPERATORS_H_ diff --git a/src/tsync-lib/test/mocks/BUILD b/src/tsync-lib/test/mocks/BUILD new file mode 100644 index 0000000..3435c6f --- /dev/null +++ b/src/tsync-lib/test/mocks/BUILD @@ -0,0 +1,13 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "mocks", + hdrs = [ + "ConsumerTimeBaseValidationNotificationMock.h", + ], + strip_include_prefix = ".", + deps = [ + "//src/tsync-lib:tsync_if", + ], + visibility = ["//visibility:public"] +) diff --git a/src/tsync-lib/test/mocks/ConsumerTimeBaseValidationNotificationMock.h b/src/tsync-lib/test/mocks/ConsumerTimeBaseValidationNotificationMock.h new file mode 100644 index 0000000..c600d19 --- /dev/null +++ b/src/tsync-lib/test/mocks/ConsumerTimeBaseValidationNotificationMock.h @@ -0,0 +1,23 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_CONSUMERTIMEBASEVALIDATIONNOTIFICATIONMOCK_H_ +#define SCORE_TIME_CONSUMERTIMEBASEVALIDATIONNOTIFICATIONMOCK_H_ + +#include + +#include "score/time/consumer_time_base_validation_notification.h" + +namespace score { +namespace time { + +class ConsumerTimeBaseValidationNotificationMock final : public ConsumerTimeBaseValidationNotification { + MOCK_METHOD1(SetPdelayInitiatorData, void(const PdelayInitiatorMeasurementType&)); + MOCK_METHOD1(SetSlaveTimingData, void(const TimeSlaveMeasurementType&)); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_CONSUMERTIMEBASEVALIDATIONNOTIFICATIONMOCK_H_ diff --git a/src/tsync-ptp-lib/BUILD b/src/tsync-ptp-lib/BUILD new file mode 100644 index 0000000..af4fc48 --- /dev/null +++ b/src/tsync-ptp-lib/BUILD @@ -0,0 +1,76 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Interface (Public) +cc_library( + name = "interface", + hdrs = glob([ + "include/**/*.h", + ]), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) +# Internal Components (Private/Subpackage visibility) +cc_library( + name = "config", + srcs = [ + "src/tsync_config.cpp", + ], + deps = [ + ":interface", + ":data_broker", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common:common_if", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "data_broker", + srcs = [ + "src/tsync_data_broker.cpp", + ], + hdrs = [ + "src/tsync_data_broker.h", + ], + strip_include_prefix = "src", + deps = [ + ":interface", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common:common_if", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "impl", + srcs = [ + "src/tsync_ptp_lib.cpp", + ], + deps = [ + ":interface", + ":data_broker", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common:common_if", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +#Main Library +cc_library( + name = "tsync-ptp-lib", + deps = [ + ":interface", + ":config", + ":data_broker", + ":impl", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/common:common_if", + ], + visibility = ["//visibility:public"], +) diff --git a/src/tsync-ptp-lib/include/tsync_ptp_lib.h b/src/tsync-ptp-lib/include/tsync_ptp_lib.h new file mode 100644 index 0000000..ce34666 --- /dev/null +++ b/src/tsync-ptp-lib/include/tsync_ptp_lib.h @@ -0,0 +1,133 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef TSYNC_PTP_LIB_H_INCLUDED +#define TSYNC_PTP_LIB_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +// coverity[autosar_cpp14_a1_1_1_violation] This library provides a C API, the use of C headers is is acceptable. + +#include "tsync_ptp_lib_types.h" + +/** + * @brief Performs basic initialization of the tsync-ptp-lib + * + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_Open(void); + +/** + * @brief Shuts down the tsync-ptp-lib + */ +void TSync_Close(void); + +/** + * @brief Opens a timebase for reading/writing + * + * @param timeBaseId The timebase identifier of the timebase to open. + * @return TSync_TimeBaseHandleType Timebase handle or TSYNC_INVALID_HANDLE, + * if the timebase could not be openend + */ +TSync_TimeBaseHandleType TSync_OpenTimebase(TSync_SynchronizedTimeBaseType timeBaseId); + +/** + * @brief Closes and invalidates a given timebase handle + * + * @param timeBaseHandle + */ +void TSync_CloseTimebase(TSync_TimeBaseHandleType timeBaseHandle); + +/** @brief Allows the Time Base Provider Modules to forward a new Global Time + * tuple. + * @details + * This function shall forward a new Global Time tuple. + * @param[in] timeBaseHandle Handle to Time Base + * @param[in] globalTimePtr New Global Time value + * @param[in] userDataPtr New User Data (if not NULL) + * @param[in] measureDataPtr New measurement data + * @param[in] localTimePtr Value of the Virtual Local Time associated to + * the new Global Time + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_BusSetGlobalTime(TSync_TimeBaseHandleType timeBaseHandle, + const TSync_TimeStampType* globalTimePtr, const TSync_UserDataType* userDataPtr, + const TSync_MeasurementType* measureDataPtr, + const TSync_VirtualLocalTimeType* localTimePtr); + +/** @brief Allows the Time Base Provider and Consumer Module to get a Global Time + * tuple. + * @details + * This function shall get a new Global Time tuple. + * @param[in] timeBaseHandle Handle to Time Base + * @param[in] globalTimePtr New Global Time value + * @param[in] userDataPtr New User Data (if not NULL) + * @param[in] measureDataPtr New measurement data + * @param[in] localTimePtr Value of the Virtual Local Time associated to + * the new Global Time + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_BusGetGlobalTime(TSync_TimeBaseHandleType timeBaseHandle, TSync_TimeStampType* globalTimePtr, + TSync_UserDataType* userDataPtr, TSync_MeasurementType* measureDataPtr, + TSync_VirtualLocalTimeType* localTimePtr); + +/** @brief Returns the Virtual Local Time of the referenced Time Base + * + * @param[in] timeBaseId Time Base reference + * @param[out] localTimePtr Current Virtual Local Time value + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_GetCurrentVirtualLocalTime(TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr); + +/** + * @brief Registers a function for triggering transmission of timestamp updates on the bus + * @details + * Registered callbacks will be automatically unregistered by calling TSync_CloseTimebase + * Registering the same callback twice will fail. + * @param[in] timeBaseHandle Handle to Time Base + * @param[in] cb Callback function + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_RegisterTransmitGlobalTimeCallback(TSync_TimeBaseHandleType timeBaseHandle, + TSync_TransmitGlobalTimeCallback cb); + +/** + * @brief Sets the timeout status bit to 1 for the given timebase + * @details + * The TimeBase consumer independently monitors for the timeout periodically. When no timebase + * update is received for , this function is called to + * set the timeout bit. + * @param[in] timeBaseHandle Handle to Time Base + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_SetTimeoutStatus(TSync_TimeBaseHandleType timeBaseHandle); + +/** + * @brief Gets the timebase configuration for the given timebase + * @details + * The configuration for the given timebase is read and filled into the passed in configuration + * structure. + * @param[in] timeBaseHandle Handle to Time Base + * @param[in] role_requested Role of the Time Base + * @param[out] TSync_TimeBaseConfiguration timebase configuration + * @return TSync_ReturnType E_OK: successful + * E_NOT_OK: failed + */ +TSync_ReturnType TSync_GetTimebaseConfiguration(TSync_TimeBaseHandleType timeBaseHandle, TSync_Role role_requested, + TSync_TimeBaseConfiguration* config); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TSYNC_PTP_LIB_H_INCLUDED */ diff --git a/src/tsync-ptp-lib/include/tsync_ptp_lib_types.h b/src/tsync-ptp-lib/include/tsync_ptp_lib_types.h new file mode 100644 index 0000000..fb64736 --- /dev/null +++ b/src/tsync-ptp-lib/include/tsync_ptp_lib_types.h @@ -0,0 +1,224 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef TSYNC_PTP_LIB_TYPES_H_INCLUDED +#define TSYNC_PTP_LIB_TYPES_H_INCLUDED + +// coverity[autosar_cpp14_a1_1_1_violation] This library provides a C API, the use of C headers is acceptable. +#include + +/** @brief The return type for the public API */ +typedef uint8_t TSync_ReturnType; + +/** @brief return value indicating success */ +#define E_OK 0x00U + +/** @brief return value indicating failure */ +#define E_NOT_OK 0x01U + +/** @brief Timebase identifier */ +typedef uint16_t TSync_SynchronizedTimeBaseType; + +/** @brief Callback type for TransmitGlobalTime */ +typedef TSync_ReturnType (*TSync_TransmitGlobalTimeCallback)(TSync_SynchronizedTimeBaseType); + +/** @brief Reflects the state of a timebase */ +typedef uint8_t TSync_TimeBaseStatusType; + +/** @brief Reflects an invalid handle */ +#ifdef __cplusplus +#define TSYNC_INVALID_HANDLE (static_cast(0)) +#else +#define TSYNC_INVALID_HANDLE ((TSync_TimeBaseHandleType*)0) +#endif + +/** @brief Reflects an invalid time base id */ +#define TSYNC_INVALID_TIME_BASE_ID 0xffffu + +/** @brief Timebase handle type as returned by TSync_OpenTimebase() */ +typedef void* TSync_TimeBaseHandleType; + +/** @brief Indicates a timeout has happened on receiving a synchronization + * message */ +#define TIMEBASE_STATUS_BIT_TIMEOUT 0u + +/** @brief Indicates that the local timebase is synced to a gateway and not + * the global time master */ +#define TIMEBASE_STATUS_BIT_SYNC_TO_GATEWAY 2u + +/** @brief Indicates that the local timebase was synced at least once with + * the global timebase */ +#define TIMEBASE_STATUS_BIT_GLOBAL_SYNC 3u + +/** @brief Indicates that a leap into the future of a received timebase + * exceeded a configured threshold */ +#define TIMEBASE_STATUS_BIT_TIMELEAP_FUTURE 4u + +/** @brief Indicates that a leap into the past of a received timebase + * exceeded a configured threshold */ +#define TIMEBASE_STATUS_BIT_TIMELEAP_PAST 5u + +/** @brief The size of the string holding the zero terminated instance + * specifier name */ +#define TSYNC_INSTANCE_SPECIFIER_MAX_SIZE 256U + +/* TODO: investigate possible packing/alignment issues. + TODO: #pragma pack(push, 8) might be a better choice here. */ +#pragma pack(push, 1) + +/** @brief Type for expressing timestamps in relative in absolute time */ +// clang-format off +// RULECHECKER_comment(1, 1, check_incomplete_data_member_construction, "This header offers a C API, so struct members cannot be initialized", true) +typedef struct { + // clang-format on + /** @brief Status indicator bitfield of the timestamp */ + TSync_TimeBaseStatusType timeBaseStatus; + /** @brief nanoseconds part of the time */ + uint32_t nanoseconds; + /** @brief 32 bit LSB of the 48 bits Seconds part of the time */ + uint32_t seconds; + /** @brief 16 bit MSB of the 48 bits Seconds part of the time */ + uint16_t secondsHi; +} TSync_TimeStampType; + +/** @brief Current user data of the Time Base */ +typedef struct { + /** @brief User Data Length in bytes, value range: 0..3 */ + uint8_t userDataLength; + /** @brief User Byte 0 */ + uint8_t userByte0; + /** @brief User Byte 1 */ + uint8_t userByte1; + /** @brief User Byte 2 */ + uint8_t userByte2; +} TSync_UserDataType; + +/** @brief Structure which contains additional measurement data */ +typedef struct { + /** @brief Propagation delay in nanoseconds */ + uint32_t pathDelay; +} TSync_MeasurementType; + +/** @brief Time stamps of the Virtual Local Time. The unit is nanoseconds */ +typedef struct { + /** @brief Least significant 32 bits of the 64 bit Virtual Local Time */ + uint32_t nanosecondsLo; + /** @brief Most significant 32 bits of the 64 bit Virtual Local Time */ + uint32_t nanosecondsHi; +} TSync_VirtualLocalTimeType; + +/** @brief This structure provides a mapping of timebase identifiers to + * instance specifiers. */ +typedef struct { + /** @brief A timebase identifier */ + TSync_SynchronizedTimeBaseType timeBaseId; + /** @brief The corresponding instance specifier */ + char instanceSpecifier[TSYNC_INSTANCE_SPECIFIER_MAX_SIZE + 1]; + +} TSync_TimeBaseIdentifierMappingType; + +/** @brief This structure describes the basic layout of the memory block + * holding the identifier mappings. + */ +typedef struct { + /** @brief The number of mappings will be the first 16Bit value in shared + * memmory */ + uint16_t numMappings; + /** @brief The data following right after the numMappings entry will be + * interpreted as the beginning of the mappings array. + */ + TSync_TimeBaseIdentifierMappingType mappings[1]; +} TSync_TimeBaseIdentifierMappingLayoutType; +#pragma pack(pop) + +/* Configuration data types */ + +/** @brief This enum defines the different message compliance variants. */ +typedef enum { TSYNC_MC_IEEE8021AS = 0, TSYNC_MC_IEEE8021AS_AUTOSAR } TSync_MessageCompliance; + +/** @brief This enum defines the different CRC support variants for time providers. */ +typedef enum { TSYNC_CRC_NOT_SUPPORTED = 0, TSYNC_CRC_SUPPORTED } TSync_CrcSupport; + +/** @brief This enum defines the different CRC validation variants for time consumers. */ +typedef enum { + TSYNC_CRC_VALIDATED = 0, + TSYNC_CRC_NOT_VALIDATED, + TSYNC_CRC_IGNORED, + TSYNC_CRC_OPTIONAL + +} TSync_RxCrcValidation; + +/** @brief This enum defines the possible ptpd roles. */ +typedef enum { TSYNC_ROLE_INVALID = 0, TSYNC_ROLE_MASTER, TSYNC_ROLE_SLAVE } TSync_Role; + +/** @brief This enum defines the different SubTLV support options */ +typedef enum { TSYNC_SUBTLV_NOT_SUPPORTED = 0, TSYNC_SUBTLV_SUPPORTED } TSync_SubTLVSupport; + +#define TSYNC_MAX_FUP_DATA_ID_ENTRIES 16 +#define TSYNC_MAC_ADDRESS_STRING_LENGTH 12 + +/** @brief This struct holds the SubTLV configuration. */ +typedef struct { + /** @brief holds configured Time SubTLV support as represented by the TSync_SubTLVSupport enum */ + uint32_t followUpTimeSubTLV; + /** @brief holds configured Status SubTLV support as represented by the TSync_SubTLVSupport enum */ + uint32_t followUpStatusSubTLV; + /** @brief holds configured Userdata SubTLV support as represented by the TSync_SubTLVSupport enum */ + uint32_t followUpUserDataSubTLV; +} TSync_SubTlvConfig; + +/** @brief This struct holds the CRC flags for PTP network message fields. */ +typedef struct { + /** @brief flag indicating whether the correction field will be considered + * for CRC calculation as represented by the TSync_CrcSupport enum */ + uint32_t correction_field; + /** @brief flag indicating whether the domain number field will be considered + * for CRC calculation as represented by the TSync_CrcSupport enum */ + uint32_t domain_number; + /** @brief flag indicating whether the message length field will be considered + * for CRC calculation as represented by the TSync_CrcSupport enum */ + uint32_t message_length; + /** @brief flag indicating whether the precide origin timestamp field will be considered + * for CRC calculation as represented by the TSync_CrcSupport enum */ + uint32_t precise_origin_timestamp; + /** @brief flag indicating whether the sequence id field will be considered + * for CRC calculation as represented by the TSync_CrcSupport enum */ + uint32_t sequence_id; + /** @brief flag indicating whether the source port identity field will be considered + * for CRC calculation as represented by the TSync_CrcSupport enum */ + uint32_t source_port_identity; +} TSync_CrcFlags; + +/** @brief This struct holds all the data relevant to the configuration of a timebase resource. */ +typedef struct { + uint8_t fupDataIdList[TSYNC_MAX_FUP_DATA_ID_ENTRIES]; + /** @brief holds the configured mac multicast address for ethernet tsync communication */ + char destMacAddress[TSYNC_MAC_ADDRESS_STRING_LENGTH + 1]; + /** @brief holds the configured immidiate resume time in miliseconds */ + int64_t immediateResumeTimeMs; + /** @brief holds the configured sync period in miliseconds */ + int64_t syncPeriodMs; + /** @brief holds the configured follow-up timeout value in miliseconds */ + int64_t followUpTimeoutMs; + /** @brief holds the configured message compliance as represented by the TSync_MessageCompliance enum */ + uint32_t messageCompliance; + /** @brief holds the configured vlan priority */ + uint32_t vLanPriority; + /** @brief holds the number of entries in the fup_data_id_list array. This is either 0 or 16 */ + uint32_t numFupDataIdEntries; + /** @brief holds the configured CRC support as represented by the TSync_CrcSupport enum */ + uint32_t crcSupport; + /** @brief holds the configured CRC flags */ + TSync_CrcFlags crcFlags; + /** @brief holds the configured CRC validation support as represented by the TSync_RxCrcValidation enum */ + uint32_t crcValidation; + /** @brief holds the configured ptpd role as represented by the TSync_Role enum */ + uint32_t role; + /** @brief holds the SubTLV configuration */ + TSync_SubTlvConfig subTlvConfig; + /** @brief holds the sequence counter jump width */ + uint32_t globalTimeSequenceCounterJumpWidth; +} TSync_TimeBaseConfiguration; + +#endif // TSYNC_PTP_LIB_TYPES_H_INCLUDED diff --git a/src/tsync-ptp-lib/src/tsync_config.cpp b/src/tsync-ptp-lib/src/tsync_config.cpp new file mode 100644 index 0000000..f21697a --- /dev/null +++ b/src/tsync-ptp-lib/src/tsync_config.cpp @@ -0,0 +1,131 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/ITimeBaseReader.h" +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TsyncConfigTypes.h" + +#include "tsync_data_broker.h" +#include "tsync_ptp_lib.h" + +using score::time::ITimeBaseReader; +using score::time::TimeBaseReaderFactory; +using score::time::TsyncTimeDomainConfig; +using score::time::TsyncCrcSupport; +using score::time::TsyncCrcValidation; +using score::time::TsyncEthMessageFormat; +using score::time::common::logFatalAndAbort; + + +TSync_CrcSupport Convert(TsyncCrcSupport crc_support) { + switch (crc_support) { + case TsyncCrcSupport::kSupported: + return TSYNC_CRC_SUPPORTED; + case TsyncCrcSupport::kNotSupported: + return TSYNC_CRC_NOT_SUPPORTED; + default: + return TSYNC_CRC_NOT_SUPPORTED; + } +} + +TSync_RxCrcValidation Convert(TsyncCrcValidation crc_validation) { + switch(crc_validation) { + case TsyncCrcValidation::kIgnored: + return TSYNC_CRC_IGNORED; + case TsyncCrcValidation::kNotValidated: + return TSYNC_CRC_NOT_VALIDATED; + case TsyncCrcValidation::kOptional: + return TSYNC_CRC_OPTIONAL; + case TsyncCrcValidation::kValidated: + return TSYNC_CRC_VALIDATED; + default: + return TSYNC_CRC_OPTIONAL; + } +} + +TSync_MessageCompliance Convert(TsyncEthMessageFormat msg_format) { + switch(msg_format) { + case TsyncEthMessageFormat::kIeee8021AS: + return TSYNC_MC_IEEE8021AS; + case TsyncEthMessageFormat::kIeee8021ASAutosar: + return TSYNC_MC_IEEE8021AS_AUTOSAR; + default: + return TSYNC_MC_IEEE8021AS_AUTOSAR; + } +} + +TSync_ReturnType TSync_GetTimebaseConfiguration(TSync_TimeBaseHandleType timebaseHandle, TSync_Role role_requested, TSync_TimeBaseConfiguration* config) { + if(timebaseHandle != TSYNC_INVALID_HANDLE && config != nullptr) { + TSync_DB_Timebase_Meta_Data* timebase_meta_data = static_cast(timebaseHandle); + TsyncTimeDomainConfig domain_config; + { + timebase_meta_data->reader->GetAccessor().Open(); + std::lock_guard lock(*(timebase_meta_data->reader)); + if(!timebase_meta_data->reader->Read(domain_config)) { + logFatalAndAbort("TSync_GetTimebaseConfiguration - Could not read time domain configuration."); + } + } + + if(!(domain_config.consumer_config.time_slave_config.is_valid || domain_config.provider_config.time_master_config.is_valid)) { + logFatalAndAbort("TSync_GetTimebaseConfiguration - Neither the time-consumer config nor the time-provider config is valid."); + } + + config->numFupDataIdEntries = domain_config.eth_time_domain.num_fup_data_id_entries; + if(config->numFupDataIdEntries != 0u) { + std::memcpy(&config->fupDataIdList[0],& domain_config.eth_time_domain.fup_data_id_list[0], sizeof(domain_config.eth_time_domain.fup_data_id_list)); + } + std::memcpy(&config->destMacAddress[0], &domain_config.eth_time_domain.dest_mac_address[0], sizeof(domain_config.eth_time_domain.dest_mac_address)); + config->vLanPriority = domain_config.eth_time_domain.vlan_priority; + config->crcSupport = Convert(domain_config.provider_config.time_master_config.crc_support); + config->crcValidation = Convert(domain_config.consumer_config.time_slave_config.crc_validation); + config->messageCompliance = Convert(domain_config.eth_time_domain.message_format); + config->role = domain_config.provider_config.time_master_config.is_valid ? TSYNC_ROLE_MASTER : TSYNC_ROLE_SLAVE; + config->followUpTimeoutMs = domain_config.consumer_config.time_slave_config.follow_up_timeout_value.count(); + config->immediateResumeTimeMs = domain_config.provider_config.time_master_config.immediate_resume_time.count(); + config->syncPeriodMs = domain_config.provider_config.time_master_config.sync_period.count(); + config->crcFlags.correction_field = domain_config.eth_time_domain.crcFlags.correction_field ? TSYNC_CRC_SUPPORTED : TSYNC_CRC_NOT_SUPPORTED; + config->crcFlags.domain_number = domain_config.eth_time_domain.crcFlags.domain_number ? TSYNC_CRC_SUPPORTED : TSYNC_CRC_NOT_SUPPORTED; + config->crcFlags.message_length = domain_config.eth_time_domain.crcFlags.message_length ? TSYNC_CRC_SUPPORTED : TSYNC_CRC_NOT_SUPPORTED; + config->crcFlags.precise_origin_timestamp = domain_config.eth_time_domain.crcFlags.precise_origin_timestamp ? TSYNC_CRC_SUPPORTED : TSYNC_CRC_NOT_SUPPORTED; + config->crcFlags.sequence_id = domain_config.eth_time_domain.crcFlags.sequence_id ? TSYNC_CRC_SUPPORTED : TSYNC_CRC_NOT_SUPPORTED; + config->crcFlags.source_port_identity = domain_config.eth_time_domain.crcFlags.source_port_identity ? TSYNC_CRC_SUPPORTED : TSYNC_CRC_NOT_SUPPORTED; + config->globalTimeSequenceCounterJumpWidth = + domain_config.consumer_config.time_slave_config.global_time_sequence_counter_jump_width; + +#if 0 + if((role_requested == TSYNC_ROLE_MASTER) && (domain_config.provider_config.time_master_config.is_valid == true)) { + config->subTlvConfig.followUpStatusSubTLV = domain_config.provider_config.time_master_config.sub_tlv_config.status_enabled ? + TSYNC_SUBTLV_SUPPORTED : TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpTimeSubTLV = domain_config.provider_config.time_master_config.sub_tlv_config.time_enabled ? + TSYNC_SUBTLV_SUPPORTED : TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpUserDataSubTLV = domain_config.provider_config.time_master_config.sub_tlv_config.user_data_enabled ? + TSYNC_SUBTLV_SUPPORTED : TSYNC_SUBTLV_NOT_SUPPORTED; + } else if (role_requested == TSYNC_ROLE_SLAVE) { + config->subTlvConfig.followUpStatusSubTLV = domain_config.consumer_config.time_slave_config.sub_tlv_config.status_enabled ? + TSYNC_SUBTLV_SUPPORTED : TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpTimeSubTLV = domain_config.consumer_config.time_slave_config.sub_tlv_config.time_enabled ? + TSYNC_SUBTLV_SUPPORTED : TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpUserDataSubTLV = domain_config.consumer_config.time_slave_config.sub_tlv_config.user_data_enabled ? + TSYNC_SUBTLV_SUPPORTED : TSYNC_SUBTLV_NOT_SUPPORTED; + } else { + config->subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + } +#endif + + // TODO: SubTLV features are not currently supported (for more details see #94128) + config->subTlvConfig.followUpStatusSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpTimeSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + config->subTlvConfig.followUpUserDataSubTLV = TSYNC_SUBTLV_NOT_SUPPORTED; + + return E_OK; + } + return E_NOT_OK; +} diff --git a/src/tsync-ptp-lib/src/tsync_data_broker.cpp b/src/tsync-ptp-lib/src/tsync_data_broker.cpp new file mode 100644 index 0000000..6ed0269 --- /dev/null +++ b/src/tsync-ptp-lib/src/tsync_data_broker.cpp @@ -0,0 +1,221 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "tsync_data_broker.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "score/time/common/Abort.h" +#include "score/time/common/ExcludeCoverageAdapter.h" + +#include "score/time/utility/TsyncIdMappingsHandler.h" + +using score::time::ITimeBaseReader; +using score::time::ITimeBaseWriter; +using score::time::TimeBaseReaderFactory; +using score::time::TimeBaseWriterFactory; +using score::time::TsyncIdMappingsHandler; +using score::time::TsyncTimeDomainConfig; +using score::time::common::logFatalAndAbort; + +static std::unique_ptr mappings_handler; + +/* contains the meta data of a timebase */ +TSync_DB_Timebase_Meta_Data tsync_db_timebase_meta_data[TSYNC_DB_MAX_TIMEBASES] = {}; +/* this flag is atomic for immidiate visibility of state changes in multithreaded contexts */ +/* Note: The current ptpd implementation is single threaded, so this not really required */ +static std::atomic is_initialized(false); + +// forward declarations +void tsync_db_OpenIdMappings(void); +void tsync_db_PrepareTimeBaseMetaData(void); +TSync_DB_Timebase_Meta_Data* tsync_db_GetTimeBaseMetaDataElement(TSync_SynchronizedTimeBaseType timeBaseId); +void tsync_db_InitTimeBaseMetaData(TSync_DB_Timebase_Meta_Data& meta_data); + +void tsync_db_Open() { + bool init_state_expected = false; + bool init_state_desired = true; + bool state_changed = is_initialized.compare_exchange_strong(init_state_expected, init_state_desired); + if (state_changed) { + tsync_db_OpenIdMappings(); + tsync_db_PrepareTimeBaseMetaData(); + } +} + +void tsync_db_Close() { + bool init_state_expected = true; + bool init_state_desired = false; + bool state_changed = is_initialized.compare_exchange_strong(init_state_expected, init_state_desired); + if (state_changed) { + mappings_handler.reset(); + for (uint16_t i = 0u; i < TSYNC_DB_MAX_TIMEBASES; ++i) { + tsync_db_InitTimeBaseMetaData(tsync_db_timebase_meta_data[i]); + } + } +} + +// TODO: Make sure that only one Meta_Data element exist for the provider use case. +// TODO: Print error is a provider ID is opened more then once in a process +// TODO: For the consumer use case, multiple openTimebase calls will return a +// distinct meta_pointers for the same timeBaseId. Increase the size of tsync_db_timebase_meta_data +// add a is used flag in TSync_DB_Timebase_Meta_Data and initialize all elements with flag set to unused. +// Unique semaphore names for signalling +// are needed, the address of the meta data element can be used in the name (with an offset). +TSync_DB_Timebase_Meta_Data* tsync_db_OpenTimebase(TSync_SynchronizedTimeBaseType timeBaseId) { + // TODO: Whether a time base has the role of a consumer or a provider will be encoded in the id mappings in the + // future. + + if (!is_initialized) { + return nullptr; + } + + auto timebase_meta_data = tsync_db_GetTimeBaseMetaDataElement(timeBaseId); + if (timebase_meta_data == nullptr) { + std::cerr << "tsync_db_OpenTimebase - timeBaseMetaDataElement == nullptr." << std::endl; + return nullptr; + } + + int32_t ref_count = timebase_meta_data->refCounter++; + if (ref_count > 0) { + return timebase_meta_data; + } else { + // in case another thread is still busy with closing this timebase + // clang-format off + while (timebase_meta_data->isInitialized); + // clang-format on + // We only need to do this once + auto domain_name = mappings_handler->GetDomainName(timeBaseId); + if (!domain_name) { + std::cerr << "tsync_db_OpenTimebase - could not determine domain name for timebase " << timeBaseId + << std::endl; + return nullptr; + } + + bool res = false; + auto reader = TimeBaseReaderFactory::Create(*domain_name); + if (!reader) { + std::cerr << "tsync_db_OpenTimebase - could not open reader for time domain " << *domain_name << std::endl; + logFatalAndAbort("tsync_db_OpenTimebase - could not open reader for time domain"); + } + + TsyncTimeDomainConfig cfg; + reader->GetAccessor().Open(); + { + std::lock_guard lock(*reader); + res = reader->Read(cfg); + } + + if (!res) { + reader.reset(); + std::cerr << "tsync_db_OpenTimebase - could not read config of time domain " << *domain_name << std::endl; + logFatalAndAbort("tsync_db_OpenTimebase - could not read config of time domain"); + } + + if (!cfg.provider_config.time_master_config.is_valid && !cfg.consumer_config.time_slave_config.is_valid) { + reader.reset(); + std::cerr << "tsync_db_OpenTimebase - time domain " << *domain_name + << " has neither a valid provider nor consumer configuration" << std::endl; + logFatalAndAbort( + "tsync_db_OpenTimebase - time domain has neither a valid provider nor consumer configuration"); + } + auto writer = TimeBaseWriterFactory::Create(*domain_name); + + timebase_meta_data->reader = std::move(reader); + timebase_meta_data->writer = std::move(writer); + + timebase_meta_data->isInitialized = true; + return timebase_meta_data; + } +} + +void tsync_db_CloseTimebase(TSync_DB_Timebase_Meta_Data* timeBaseMetaData) { + if (is_initialized && timeBaseMetaData && mappings_handler) { + int32_t ref_counter = --timeBaseMetaData->refCounter; + // No more reference count to timebase meta data proceed to close time base + if (ref_counter == 0) { + auto domain_name = mappings_handler->GetDomainName(timeBaseMetaData->mappingData.timeBaseId); + + if (!domain_name) { + logFatalAndAbort("tsync_db_CloseTimebase - could not determine domain name for timebase."); + } + + timeBaseMetaData->reader.reset(); + timeBaseMetaData->writer.reset(); + + // check transmitGlobalTime state + if (timeBaseMetaData->transmitGlobalTime.threadStatus != TSYNC_DB_THREAD_STATUS_DEAD) { + logFatalAndAbort( + "tsync_db_CloseTimebase - transmitGlobalTime.threadStatus != TSYNC_DB_THREAD_STATUS_DEAD."); + } + if (timeBaseMetaData->transmitGlobalTime.threadSignal != TSYNC_DB_THREAD_SIGNAL_NONE) { + logFatalAndAbort( + "tsync_db_CloseTimebase - failed transmitGlobalTime.threadSignal != TSYNC_DB_THREAD_SIGNAL_NONE."); + } + if (timeBaseMetaData->transmitGlobalTime.callback != nullptr) { + logFatalAndAbort("tsync_db_CloseTimebase - callback != nullptr"); + } + timeBaseMetaData->isInitialized = false; + } else { + if (ref_counter < 0) { + logFatalAndAbort("tsync_db_CloseTimebase - refCounter is negative"); + } + // There still reference count to timebase meta data no need for more action + return; + } + } +} + +void tsync_db_OpenIdMappings(void) { + mappings_handler = std::make_unique(); + mappings_handler->DoSharedMemoryRead(); +} + +void tsync_db_PrepareTimeBaseMetaData() { + uint32_t i = 0u; + for (auto it : *mappings_handler) { + tsync_db_InitTimeBaseMetaData(tsync_db_timebase_meta_data[i]); + tsync_db_timebase_meta_data[i].mappingData.timeBaseId = + static_cast(it.first & 0x0000ffff); + tsync_db_timebase_meta_data[i].mappingData.timeBaseRole = + TSYNC_DB_TIMEBASE_ROLE_PROVIDER; // TODO: ml->mappings[i].timeBaseRole; + ++i; + if (i == TSYNC_DB_MAX_TIMEBASES) { + break; + } + } +} + +void tsync_db_InitTimeBaseMetaData(TSync_DB_Timebase_Meta_Data& meta_data) { + meta_data.reader.reset(); + meta_data.writer.reset(); + meta_data.mappingData.timeBaseId = TSYNC_INVALID_TIME_BASE_ID; + meta_data.mappingData.timeBaseRole = TSYNC_DB_TIMEBASE_ROLE_NONE; + meta_data.transmitGlobalTime.callback = nullptr; + meta_data.transmitGlobalTime.threadSignal = TSYNC_DB_THREAD_SIGNAL_NONE; + meta_data.transmitGlobalTime.threadStatus = TSYNC_DB_THREAD_STATUS_DEAD; + meta_data.refCounter = 0; + meta_data.isInitialized = false; +} + +TSync_DB_Timebase_Meta_Data* tsync_db_GetTimeBaseMetaDataElement(TSync_SynchronizedTimeBaseType timeBaseId) { + TSync_DB_Timebase_Meta_Data* timeBaseMetaDataElement = nullptr; + + for (uint16_t i = 0u; i < TSYNC_DB_MAX_TIMEBASES; ++i) { + if (tsync_db_timebase_meta_data[i].mappingData.timeBaseId == timeBaseId) { + timeBaseMetaDataElement = &tsync_db_timebase_meta_data[i]; + break; + } + } + + return timeBaseMetaDataElement; +} diff --git a/src/tsync-ptp-lib/src/tsync_data_broker.h b/src/tsync-ptp-lib/src/tsync_data_broker.h new file mode 100644 index 0000000..3944529 --- /dev/null +++ b/src/tsync-ptp-lib/src/tsync_data_broker.h @@ -0,0 +1,87 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef TSYNC_DATA_BROKER_H_INCLUDED +#define TSYNC_DATA_BROKER_H_INCLUDED + +#include +#include +// coverity[autosar_cpp14_a1_1_1_violation] This library provides a C API, the use of C headers is acceptable. +#include +// coverity[autosar_cpp14_a1_1_1_violation] This library provides a C API, the use of C headers is acceptable. +#include + +#include + +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TimeBaseWriterFactory.h" +#include "tsync_ptp_lib_types.h" + +#define TSYNC_DB_RESULT_OK 0 +#define TSYNC_DB_RESULT_ERR_ACCESS_ID_MAPPINGS -1 +#define TSYNC_DB_RESULT_ERR_ACCESS_TIMEBASE -2 +#define TSYNC_DB_RESULT_ERR_ID_MAPPING_NOT_FOUND -3 +#define TSYNC_DB_RESULT_ERR_ID_MAPPING_BUFFER_TOO_SMALL -4 + +/* maximum number of time bases allowed */ +#define TSYNC_DB_MAX_TIMEBASES 16u + +/* size of the prefix for the semaphore name /p_ or /c_ */ +#define TSYNC_DB_SEMAPHORE_PREFIX_SIZE 3u + +/* size of the semaphore name */ +#define TSYNC_DB_SEMAPHORE_NAME_MAX_SIZE TSYNC_INSTANCE_SPECIFIER_MAX_SIZE + TSYNC_DB_SEMAPHORE_PREFIX_SIZE + +typedef int32_t TSync_DB_Result; + +typedef enum { + TSYNC_DB_TIMEBASE_ROLE_NONE, + TSYNC_DB_TIMEBASE_ROLE_CONSUMER, + TSYNC_DB_TIMEBASE_ROLE_PROVIDER +} TSync_DB_Timebase_Role; + +typedef struct { + TSync_SynchronizedTimeBaseType timeBaseId; + TSync_DB_Timebase_Role timeBaseRole; +} TSync_DB_Timebase_Mapping_Data; + +typedef enum { + TSYNC_DB_THREAD_SIGNAL_NONE, + TSYNC_DB_THREAD_SIGNAL_EXIT, + TSYNC_DB_THREAD_SIGNAL_CONTINUE +} TSync_DB_Thread_Signal; + +typedef enum { TSYNC_DB_THREAD_STATUS_DEAD, TSYNC_DB_THREAD_STATUS_ALIVE } TSync_DB_Thread_Status; +typedef struct { + pthread_t threadHandle; + std::atomic threadSignal; + std::atomic threadStatus; + TSync_TransmitGlobalTimeCallback callback; +} TSync_DB_Transmit_Global_Time_Data; + +// clang-format off +// RULECHECKER_comment(1, 8, check_incomplete_data_member_construction, "Pimpl pattern does not allow construction of all members", true) +typedef struct { + // clang-format on + TSync_DB_Timebase_Mapping_Data mappingData; + score::time::TimeBaseReaderFactory::PointerType reader; + score::time::TimeBaseWriterFactory::PointerType writer; + TSync_DB_Transmit_Global_Time_Data transmitGlobalTime; + std::atomic refCounter; + std::atomic isInitialized; +} TSync_DB_Timebase_Meta_Data; + +/* Accesses the shared memory region holding the timeBaseIdentfier - InstanceSpecifier mappings */ +void tsync_db_Open(); +/* Rolls back tsync_db_Open() actions */ +void tsync_db_Close(); +/* Opens the shared memory region associated with the given timeBase identifier +and sets up the returned data structure */ +TSync_DB_Timebase_Meta_Data* tsync_db_OpenTimebase(TSync_SynchronizedTimeBaseType timeBaseId); +/* Closes the shared memory region associated with the given timeBase handle */ +void tsync_db_CloseTimebase(TSync_DB_Timebase_Meta_Data* timeBaseHandle); +/* required for UTs */ +void tsync_db_lib_unlock(); + +#endif // TSYNC_DATA_BROKER_H_INCLUDED diff --git a/src/tsync-ptp-lib/src/tsync_ptp_lib.cpp b/src/tsync-ptp-lib/src/tsync_ptp_lib.cpp new file mode 100644 index 0000000..3d11619 --- /dev/null +++ b/src/tsync-ptp-lib/src/tsync_ptp_lib.cpp @@ -0,0 +1,360 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "tsync_ptp_lib.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/ITimeBaseReader.h" +#include "score/time/utility/ITimeBaseWriter.h" +#include "score/time/utility/SysCalls.h" +#include "score/time/utility/TsyncSharedUtils.h" + +#include "tsync_data_broker.h" + +using score::time::AlignedSkip; +using score::time::ITimeBaseReader; +using score::time::ITimeBaseWriter; +using score::time::SynchronizationStatus; +using score::time::TsyncSharedUtils; +using score::time::UserData; +using score::time::UserDataView; + +using score::time::OsThreadCreate; +using score::time::OsThreadJoin; +using score::time::OsUmask; + +using score::time::common::logFatalAndAbort; + +TSync_TimeBaseStatusType GetTimeStampStatusFromSyncStatus(SynchronizationStatus input) { + TSync_TimeBaseStatusType res; + if (input == SynchronizationStatus::kSynchronized) { + res = 1U << TIMEBASE_STATUS_BIT_GLOBAL_SYNC; + } else if (input == SynchronizationStatus::kSynchToGateway) { + res = 1U << TIMEBASE_STATUS_BIT_SYNC_TO_GATEWAY; + } else if (input == SynchronizationStatus::kTimeOut) { + res = 1U << TIMEBASE_STATUS_BIT_TIMEOUT; + } else { + res = 0U; + } + return res; +} + +TSync_ReturnType TSync_Open(void) { + tsync_db_Open(); + + return TSYNC_DB_RESULT_OK; +} + +void TSync_Close(void) { + tsync_db_Close(); +} + +TSync_TimeBaseHandleType TSync_OpenTimebase(TSync_SynchronizedTimeBaseType timeBaseId) { + return static_cast(tsync_db_OpenTimebase(timeBaseId)); +} + +void TSync_CloseTimebase(TSync_TimeBaseHandleType timeBaseHandle) { + if (TSync_RegisterTransmitGlobalTimeCallback(timeBaseHandle, nullptr) == E_OK) { + tsync_db_CloseTimebase(static_cast(timeBaseHandle)); + } +} + +// clang-format off +// RULECHECKER_comment(1, 1, check_max_parameters, "The function signature reflects a pre-existing AUTOSAR Classic API.", true) +TSync_ReturnType TSync_BusSetGlobalTime(TSync_TimeBaseHandleType timeBaseHandle, + const TSync_TimeStampType* globalTimePtr, const TSync_UserDataType* userDataPtr, + const TSync_MeasurementType* measureDataPtr, + const TSync_VirtualLocalTimeType* localTimePtr) { + // clang-format on + (void)measureDataPtr; + bool res = false; + + if (timeBaseHandle != TSYNC_INVALID_HANDLE) { + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(timeBaseHandle); + if (!timeBaseMetaData->isInitialized) { + return E_NOT_OK; + } + score::time::SynchronizationStatus ts_mem; + { + // read shared memory to obtain current status + timeBaseMetaData->reader->GetAccessor().Open(); + std::lock_guard r_lock(*(timeBaseMetaData->reader)); + res = timeBaseMetaData->reader->Read(ts_mem); + } + + if (!res) { + return E_NOT_OK; + } + + timeBaseMetaData->writer->GetAccessor().Open(); + std::lock_guard w_lock(*(timeBaseMetaData->writer)); + + if (globalTimePtr) { + TSync_TimeStampType ts = *globalTimePtr; + score::time::TimestampWithStatus ts_w_status; + if (ts_mem == SynchronizationStatus::kSynchToGateway) { + ts_w_status.status = ts_mem; + } else { + ts_w_status.status = SynchronizationStatus::kSynchronized; + } + + ts_w_status.seconds = std::chrono::seconds((static_cast(ts.secondsHi) << 32) | + static_cast(ts.seconds)); + ts_w_status.nanoseconds = + std::chrono::nanoseconds(static_cast(ts.nanoseconds)); + + res = timeBaseMetaData->writer->Write(ts_w_status); + } else { + res = AlignedSkip(*(timeBaseMetaData->writer)); + } + + if (!res) { + return E_NOT_OK; + } + + if (localTimePtr) { + std::chrono::nanoseconds vlt = std::chrono::nanoseconds( + (static_cast(localTimePtr->nanosecondsHi) << 32) | + static_cast(localTimePtr->nanosecondsLo)); + + res = timeBaseMetaData->writer->Write(vlt); + } else { + res = AlignedSkip(*(timeBaseMetaData->writer)); + } + + if (!res) { + return E_NOT_OK; + } + + if (userDataPtr) { + UserData ud; + ud[0U] = std::byte{userDataPtr->userDataLength}; + ud[1U] = std::byte{userDataPtr->userByte0}; + ud[2U] = std::byte{userDataPtr->userByte1}; + ud[3U] = std::byte{userDataPtr->userByte2}; + + res = timeBaseMetaData->writer->Write(ud); + } + } + + return res ? E_OK : E_NOT_OK; +} + +// clang-format off +// RULECHECKER_comment(1, 1, check_max_parameters, "The function signature reflects a pre-existing AUTOSAR Classic API.", true) +TSync_ReturnType TSync_BusGetGlobalTime(TSync_TimeBaseHandleType timeBaseHandle, TSync_TimeStampType* globalTimePtr, + TSync_UserDataType* userDataPtr, TSync_MeasurementType* measureDataPtr, + TSync_VirtualLocalTimeType* localTimePtr) { + // clang-format on + (void)measureDataPtr; + bool res = false; + + if (timeBaseHandle == TSYNC_INVALID_HANDLE) { + return E_NOT_OK; + } + + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(timeBaseHandle); + if (!timeBaseMetaData->isInitialized) { + return E_NOT_OK; + } + timeBaseMetaData->reader->GetAccessor().Open(); + std::lock_guard lock(*timeBaseMetaData->reader); + + if (globalTimePtr) { + score::time::TimestampWithStatus ts_w_status; + res = timeBaseMetaData->reader->Read(ts_w_status); + if (res) { + globalTimePtr->seconds = static_cast(ts_w_status.seconds.count() & 0xffffffff); + globalTimePtr->secondsHi = static_cast((ts_w_status.seconds.count() >> 32) & 0xffff); + globalTimePtr->nanoseconds = static_cast(ts_w_status.nanoseconds.count() & 0xffffffff); + globalTimePtr->timeBaseStatus = GetTimeStampStatusFromSyncStatus(ts_w_status.status); + } + } else { + res = AlignedSkip(*(timeBaseMetaData->reader)); + } + + if (!res) { + return E_NOT_OK; + } + + if (localTimePtr) { + std::chrono::nanoseconds vlt; + res = timeBaseMetaData->reader->Read(vlt); + if (res) { + localTimePtr->nanosecondsHi = static_cast((vlt.count() & 0xffffffff00000000) >> 32); + localTimePtr->nanosecondsLo = static_cast(vlt.count() & 0xffffffff); + } + } else { + res = AlignedSkip(*(timeBaseMetaData->reader)); + } + + if (!res) { + return E_NOT_OK; + } + + if (userDataPtr) { + UserDataView udv{}; + res = timeBaseMetaData->reader->Read(udv); + if (!res) { + return E_NOT_OK; + } + userDataPtr->userDataLength = static_cast(udv.size()); + if (userDataPtr->userDataLength > 0) { + userDataPtr->userByte0 = static_cast(udv[0U]); + if (userDataPtr->userDataLength > 1) { + userDataPtr->userByte1 = static_cast(udv[1U]); + if (userDataPtr->userDataLength > 2) { + userDataPtr->userByte2 = static_cast(udv[2U]); + } + } + } + } + + return E_OK; +} + +TSync_ReturnType TSync_GetCurrentVirtualLocalTime(TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr) { + if ((timeBaseHandle != TSYNC_INVALID_HANDLE) && (localTimePtr != nullptr)) { + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(timeBaseHandle); + if (timeBaseMetaData->isInitialized) { + auto ns = score::time::TsyncSharedUtils::GetCurrentVirtualLocalTime(); + + if (ns) { + localTimePtr->nanosecondsLo = static_cast(ns->count() & 0xffffffff); + localTimePtr->nanosecondsHi = static_cast((ns->count() & 0xffffffff00000000) >> 32); + return E_OK; + } + } + } + + return E_NOT_OK; +} + +// coverity[autosar_cpp14_a8_4_10_violation] This function is used in an OS-provided function. +void* TSync_TransmitGlobalTimeRunner(void* arg) { + if (arg == nullptr) { + logFatalAndAbort("TSync_TransmitGlobalTimeRunner called with nullptr argument"); + } + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(arg); + if (!timeBaseMetaData->isInitialized) { + return nullptr; + } + + mode_t temp_umask = OsUmask(0111U); + auto sem = TsyncSharedUtils::CreateTransmissionSemaphore(timeBaseMetaData->mappingData.timeBaseId, false); + (void)OsUmask(temp_umask); + + while (timeBaseMetaData->transmitGlobalTime.threadSignal.load() != TSYNC_DB_THREAD_SIGNAL_EXIT && + timeBaseMetaData->isInitialized) { + sem.lock(); + + TSync_TransmitGlobalTimeCallback transmitGlobalTimeCallback = timeBaseMetaData->transmitGlobalTime.callback; + if (transmitGlobalTimeCallback != nullptr && + timeBaseMetaData->transmitGlobalTime.threadSignal.load() != TSYNC_DB_THREAD_SIGNAL_EXIT) { + transmitGlobalTimeCallback(timeBaseMetaData->mappingData.timeBaseId); + } + } + + timeBaseMetaData->transmitGlobalTime.threadStatus.store(TSYNC_DB_THREAD_STATUS_DEAD); + return nullptr; +} + +void TSync_HandleCallBackThreadCleanup(TSync_TimeBaseHandleType timeBaseHandle) { + if (timeBaseHandle != TSYNC_INVALID_HANDLE) { + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(timeBaseHandle); + + if (timeBaseMetaData->transmitGlobalTime.threadStatus.load() == TSYNC_DB_THREAD_STATUS_ALIVE) { + timeBaseMetaData->transmitGlobalTime.threadSignal.store(TSYNC_DB_THREAD_SIGNAL_EXIT); + + // wake the thread + auto sem = TsyncSharedUtils::CreateTransmissionSemaphore(timeBaseMetaData->mappingData.timeBaseId, false); + sem.unlock(); + + auto res = OsThreadJoin(timeBaseMetaData->transmitGlobalTime.threadHandle, nullptr); + if (res != 0) { + std::perror("TSync_HandleCallBackThreadCleanup - pthread_join failed"); + logFatalAndAbort("TSync_HandleCallBackThreadCleanup - pthread_join failed"); + } + + timeBaseMetaData->transmitGlobalTime.callback = nullptr; + timeBaseMetaData->transmitGlobalTime.threadSignal.store(TSYNC_DB_THREAD_SIGNAL_NONE); + } + } +} + +TSync_ReturnType TSync_RegisterTransmitGlobalTimeCallback(TSync_TimeBaseHandleType timeBaseHandle, + TSync_TransmitGlobalTimeCallback cb) { + if (timeBaseHandle != TSYNC_INVALID_HANDLE) { + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(timeBaseHandle); + if (!timeBaseMetaData->isInitialized) { + return E_NOT_OK; + } + + TSync_TransmitGlobalTimeCallback old_callback = timeBaseMetaData->transmitGlobalTime.callback; + + if (cb == old_callback) { + return E_OK; + } + + if (cb == nullptr) { + TSync_HandleCallBackThreadCleanup(timeBaseHandle); + } + + if (cb != nullptr && old_callback == nullptr) { // register callback once + timeBaseMetaData->transmitGlobalTime.callback = cb; + timeBaseMetaData->transmitGlobalTime.threadStatus.store(TSYNC_DB_THREAD_STATUS_ALIVE); + int res = OsThreadCreate(&timeBaseMetaData->transmitGlobalTime.threadHandle, nullptr, + &TSync_TransmitGlobalTimeRunner, timeBaseHandle); + if (res != 0) { + std::perror("TSync_RegisterTransmitGlobalTimeCallback - pthread_create failed"); + logFatalAndAbort("TSync_RegisterTransmitGlobalTimeCallback - pthread_create failed"); + } + } + + return E_OK; + } + + return E_NOT_OK; +} + +TSync_ReturnType TSync_SetTimeoutStatus(TSync_TimeBaseHandleType timeBaseHandle) { + if (timeBaseHandle != TSYNC_INVALID_HANDLE) { + TSync_DB_Timebase_Meta_Data* timeBaseMetaData = static_cast(timeBaseHandle); + if (!timeBaseMetaData->isInitialized) { + return E_NOT_OK; + } + // Timeout bit must be set only when the timebase was synchronized atleast once + { + score::time::TimestampWithStatus ts; + timeBaseMetaData->reader->GetAccessor().Open(); + timeBaseMetaData->reader->lock(); + if (timeBaseMetaData->reader->Read(ts)) { + timeBaseMetaData->reader->unlock(); + if (ts.status == score::time::SynchronizationStatus::kSynchronized) { + ts.status = score::time::SynchronizationStatus::kTimeOut; + timeBaseMetaData->writer->GetAccessor().Open(); + std::lock_guard lock(*(timeBaseMetaData->writer)); + timeBaseMetaData->writer->Write(ts); + } + } else { + timeBaseMetaData->reader->unlock(); + logFatalAndAbort("TSync_SetTimeoutStatus() - could not read timestamp for timebase"); + } + } + return E_OK; + } + return E_NOT_OK; +} diff --git a/src/tsync-ptp-lib/test/tsync-ptp-lib_UT/BUILD b/src/tsync-ptp-lib/test/tsync-ptp-lib_UT/BUILD new file mode 100644 index 0000000..99cb646 --- /dev/null +++ b/src/tsync-ptp-lib/test/tsync-ptp-lib_UT/BUILD @@ -0,0 +1,17 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "tsync-ptp-lib_UT", + srcs = [ + "tsync-ptp-lib_UT.cpp", + ], + deps = [ + "//src/tsync-ptp-lib", + "//src/tsync-utility-lib:utility_tsync_id_mappings_handler", + "//src/tsync-utility-lib:utility_tsync_shared_utils", + "//src/tsync-utility-lib:utility_tsync_named_semaphore", + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-lib/test/matchers:test_matchers", + "@googletest//:gtest_main" + ] +) diff --git a/src/tsync-ptp-lib/test/tsync-ptp-lib_UT/tsync-ptp-lib_UT.cpp b/src/tsync-ptp-lib/test/tsync-ptp-lib_UT/tsync-ptp-lib_UT.cpp new file mode 100644 index 0000000..7d59f16 --- /dev/null +++ b/src/tsync-ptp-lib/test/tsync-ptp-lib_UT/tsync-ptp-lib_UT.cpp @@ -0,0 +1,1895 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "tsync_data_broker.h" +#include "tsync_ptp_lib.h" +#include "score/time/utility/TsyncConfigTypes.h" +#include "score/time/utility/SysCalls.h" +#include "score/time/utility/TsyncIdMappingsHandler.h" +#include "score/time/utility/TsyncNamedSemaphore.h" +#include "score/time/utility/TsyncSharedUtils.h" + +using score::time::OsSemOpen; + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SharedMemLayout.h" +#include "matcher_operators.h" +#include "SharedMemTimeBaseReaderMock.h" +#include "SharedMemTimeBaseWriterMock.h" +#include "SysCallsMiscMock.h" +#include "SysCallsNamedSemMock.h" +#include "SysCallsReadWriteLockMock.h" +#include "SysCallsShMemMock.h" +#include "TimeBaseReaderFactoryMock.h" +#include "TimeBaseWriterFactoryMock.h" + +using score::time::kIdMappingsShmemFileName; +using score::time::kIdMappingsShmemSize; +using score::time::kSharedMemPageSize; +using score::time::TsyncNamedSemaphore; + +#define NUM_FAKE_TIMEBASE_ID_MAPPINGS 3 + +#define FAKE_TIMEBASE_ID_0 (static_cast(1)) +#define FAKE_TIMEBASE_ID_1 (static_cast(2)) +#define FAKE_TIMEBASE_ID_2 (static_cast(3)) +#define FAKE_INSTANCE_SPECIFIER_0 "fakeId0" +#define FAKE_INSTANCE_SPECIFIER_1 "fakeId1" +#define FAKE_INSTANCE_SPECIFIER_2 "/fakeId2" + +constexpr int32_t kNumTimeDomains{16}; +const uint16_t kTimeDomainIds[kNumTimeDomains] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + +static constexpr std::uint64_t kConsumerMagic_ = 0xabcddcbaabcddcba; +static constexpr std::uint64_t kProviderMagic_ = 0x1122334444332211; + +const std::string_view kTimeDomainNames[kNumTimeDomains] = { + "domain_1", "domain_2", "domain_3", "domain_4", "domain_5", "domain_6", "domain_7", "domain_8", + "domain_9", "domain_10", "domain_11", "domain_12", "domain_13", "domain_14", "domain_15", "domain_16", +}; + +extern TSync_DB_Result tsync_db_OpenIdMappings(void); + +void TSync_HandleCallBackThreadCleanup(TSync_TimeBaseHandleType timeBaseHandle); + +extern TSync_DB_Timebase_Meta_Data tsync_db_timebase_meta_data[TSYNC_DB_MAX_TIMEBASES]; + +extern void* TSync_TransmitGlobalTimeRunner(void* arg); +extern TSync_DB_Timebase_Meta_Data* tsync_db_GetTimeBaseMetaDataElement(TSync_SynchronizedTimeBaseType timeBaseId); + +using testing::_; +using testing::An; +using testing::ByMove; +using testing::DoAll; +using testing::DoDefault; +using testing::Eq; +using testing::HasSubstr; +using testing::Invoke; +using testing::Mock; +using testing::NiceMock; +using testing::Return; +using testing::ReturnPointee; +using testing::Sequence; + +namespace score { +namespace time { +std::unique_ptr> rw_lock_mock; +std::unique_ptr> named_semaphore_mock; +std::unique_ptr> misc_mock; +std::unique_ptr> writer_factory_mock; +std::unique_ptr> reader_factory_mock; +} // namespace time +} // namespace score + +using score::time::ITimeBaseReader; +using score::time::ITimeBaseWriter; +using score::time::misc_mock; +using score::time::named_semaphore_mock; +using score::time::reader_factory_mock; +using score::time::reader_factory_mock_return_real_reader; +using score::time::rw_lock_mock; +using score::time::shared_mem_mock; +using score::time::SharedMemTimeBaseReaderMock; +using score::time::SharedMemTimeBaseWriterMock; +using score::time::SysCallsMiscMock; +using score::time::SysCallsNamedSemMock; +using score::time::SysCallsReadWriteLockMock; +using score::time::SysCallsShMemMock; +using score::time::TimeBaseReaderFactory; +using score::time::TimeBaseReaderFactoryMock; +using score::time::TimeBaseWriterFactory; +using score::time::TimeBaseWriterFactoryMock; +using score::time::TsyncCrcSupport; +using score::time::TsyncCrcValidation; +using score::time::TsyncEthMessageFormat; +using score::time::TsyncIdMappingsHandler; +using score::time::TsyncTimeDomainConfig; +using score::time::writer_factory_mock; +using score::time::writer_factory_mock_return_real_writer; + +static uint64_t g_fake_thread_id{0}; +static std::map g_fake_thread_args; + +int CreateThreadFake(pthread_t* thread, [[maybe_unused]] const pthread_attr_t* attr, + [[maybe_unused]] void* (*start_routine)(void*), [[maybe_unused]] void* arg) { + *thread = static_cast(++g_fake_thread_id); + g_fake_thread_args[*thread] = arg; // Store the metadata pointer for later + return 0; +} + +sem_t* SemOpenFake1(const char* name, int oflag) { + return sem_open(name, oflag); +} + +sem_t* SemOpenFake2(const char* name, int oflag, mode_t mode, unsigned int value) { + return sem_open(name, oflag, mode, value); +} + +int SemCloseFake(sem_t* sem) { + return sem_close(sem); +} + +int SemUnlinkFake(const char* name) { + return sem_unlink(name); +} + +int SemWaitFake(sem_t* sem) { + return sem_wait(sem); +} + +int SemPostFake(sem_t* sem) { + return sem_post(sem); +} + +int SemTryWaitFake(sem_t* sem) { + return sem_trywait(sem); +} + +int SemGetValueFake(sem_t* sem, int* val) { + return sem_getvalue(sem, val); +} + +int ThreadJoinFake(pthread_t thread, void** retval) { + if (retval) { + *retval = nullptr; + } + + // This simulates what TSync_TransmitGlobalTimeRunner does when it exits + auto it = g_fake_thread_args.find(thread); + if (it != g_fake_thread_args.end()) { + void* metadata_ptr = it->second; + TSync_DB_Timebase_Meta_Data* meta = static_cast(metadata_ptr); + meta->transmitGlobalTime.threadStatus.store(TSYNC_DB_THREAD_STATUS_DEAD); + g_fake_thread_args.erase(it); + } + + return 0; +} + +constexpr int32_t ABORT_CODE = 42; + +class PtpLibTestFixture : public testing::Test { +public: + void SetUp() override { + std::memset(&shmem_buf0_[0], 0, sizeof(shmem_buf0_)); + std::memset(&shmem_buf1_[0], 0, sizeof(shmem_buf1_)); + std::memset(&shmem_buf2_[0], 0, sizeof(shmem_buf2_)); + std::memset(&shmem_buf_id_mappings[0], 0, sizeof(shmem_buf_id_mappings)); + + // using real readers and writers, the syscalls are mocked + reader_factory_mock_return_real_reader = true; + writer_factory_mock_return_real_writer = true; + + // Create mocks + writer_factory_mock = std::make_unique>(); + reader_factory_mock = std::make_unique>(); + named_semaphore_mock = std::make_unique>(); + shared_mem_mock = std::make_unique>(); + rw_lock_mock = std::make_unique>(); + misc_mock = std::make_unique>(); + + // Setup default expectations for success cases + // Named semaphore mock + ON_CALL(*named_semaphore_mock, OsSemOpen(_, _)).WillByDefault(Invoke(SemOpenFake1)); + ON_CALL(*named_semaphore_mock, OsSemOpen(_, _, _, _)).WillByDefault(Invoke(SemOpenFake2)); + ON_CALL(*named_semaphore_mock, OsSemClose(_)).WillByDefault(Invoke(SemCloseFake)); + ON_CALL(*named_semaphore_mock, OsSemUnlink(_)).WillByDefault(Invoke(SemUnlinkFake)); + ON_CALL(*named_semaphore_mock, OsSemWait(_)).WillByDefault(Invoke(SemWaitFake)); + ON_CALL(*named_semaphore_mock, OsSemTryWait(_)).WillByDefault(Invoke(SemTryWaitFake)); + ON_CALL(*named_semaphore_mock, OsSemPost(_)).WillByDefault(Invoke(SemPostFake)); + ON_CALL(*named_semaphore_mock, OsSemGetValue(_, _)).WillByDefault(Invoke(SemGetValueFake)); + + // Shared memory mock + // different filenames return different file desriptors + ON_CALL(*shared_mem_mock, OsShmOpen(HasSubstr(kIdMappingsShmemFileName), _, _)) + .WillByDefault(Return(file_descriptor_map_[kIdMappingsShmemFileName])); + ON_CALL(*shared_mem_mock, OsShmOpen(HasSubstr(FAKE_INSTANCE_SPECIFIER_0), _, _)) + .WillByDefault(Return(file_descriptor_map_["/" FAKE_INSTANCE_SPECIFIER_0])); + ON_CALL(*shared_mem_mock, OsShmOpen(HasSubstr(FAKE_INSTANCE_SPECIFIER_1), _, _)) + .WillByDefault(Return(file_descriptor_map_["/" FAKE_INSTANCE_SPECIFIER_1])); + ON_CALL(*shared_mem_mock, OsShmOpen(HasSubstr(FAKE_INSTANCE_SPECIFIER_2), _, _)) + .WillByDefault(Return(file_descriptor_map_["/" FAKE_INSTANCE_SPECIFIER_2])); + + // different filedescriptors return different memory buffers + ON_CALL(*shared_mem_mock, OsMmap(_, _, _, _, Eq(1), _)).WillByDefault(Return(timebase_shmem_map_[1])); + ON_CALL(*shared_mem_mock, OsMmap(_, _, _, _, Eq(2), _)).WillByDefault(Return(timebase_shmem_map_[2])); + ON_CALL(*shared_mem_mock, OsMmap(_, _, _, _, Eq(3), _)).WillByDefault(Return(timebase_shmem_map_[3])); + ON_CALL(*shared_mem_mock, OsMmap(_, _, _, _, Eq(4), _)).WillByDefault(Return(timebase_shmem_map_[4])); + + ON_CALL(*shared_mem_mock, OsShmUnlink(_)).WillByDefault(Return(0)); + ON_CALL(*shared_mem_mock, OsFtruncate(_, _)).WillByDefault(Return(0)); + ON_CALL(*shared_mem_mock, OsClose(_)).WillByDefault(Return(0)); + + // Read Write lock mock + ON_CALL(*rw_lock_mock, OsRwLockTryReadLock(_)).WillByDefault(Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockReadLock(_)).WillByDefault(Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockTryWriteLock(_)).WillByDefault(Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockUnlock(_)).WillByDefault(Return(0)); + + // Time relevent mock + ON_CALL(*misc_mock, OsClockGetTime(_, _)).WillByDefault(Return(0)); + + // Thread relevent mock + ON_CALL(*misc_mock, OsThreadCreate(_, _, _, _)).WillByDefault(Invoke(CreateThreadFake)); + ON_CALL(*misc_mock, OsThreadJoin(_, _)).WillByDefault(Invoke(ThreadJoinFake)); + + Mock::AllowLeak(named_semaphore_mock.get()); + Mock::AllowLeak(misc_mock.get()); + + ts_.timeBaseStatus = TIMEBASE_STATUS_BIT_GLOBAL_SYNC; + ts_.seconds = 0x98765432; + ts_.secondsHi = 0x9199; + ts_.nanoseconds = 0xbbaaccdd; + + vt_.nanosecondsHi = 0x1234; + vt_.nanosecondsLo = 0x5678; + + ud_.userDataLength = 3; + ud_.userByte0 = 1; + ud_.userByte1 = 2; + ud_.userByte2 = 3; + + std::signal(SIGABRT, &AbortHandler); + // std::atexit(ExitHandler); + + // Create fake timebase id mappings + TsyncTimeDomainConfig cfg; + cfg.consumer_config.time_slave_config.is_valid = true; + FillTimebaseIdMappings(); + for (auto it : id_mappings_map_) { + WriteConfig(it.second, cfg); + } + + InitSemaphores(); + } + + void TearDown() override { + DeleteSemaphores(); + mappings_handler_.Clear(); + ExitHandler(); + std::signal(SIGABRT, SIG_DFL); + } + + static void ExitHandler() { + // make sure to call Tsync_Close() before invalidating the syscall mocks + // This will allow internal static objects to be cleaned off properly + TSync_Close(); + // the mocks have to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + reader_factory_mock.reset(); + writer_factory_mock.reset(); + named_semaphore_mock.reset(); + rw_lock_mock.reset(); + shared_mem_mock.reset(); + misc_mock.reset(); + g_fake_thread_args.clear(); + } + + static void AbortHandler(int /*signal*/) noexcept { + // the mock has to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + ExitHandler(); + std::exit(ABORT_CODE); + } + + void InvalidateTimeBaseMappings() { + mappings_handler_.Clear(); + mappings_handler_.CommitMappingsToSharedMemory(); + } + + void InitSemaphores() { + // tsyncd is usually the creator/owner of theses semaphores. + // creating them here as part of the infrastructure setup + for (uint16_t id = 0; id < kNumTimeDomains; ++id) { + semaphores_.push_back(score::time::TsyncSharedUtils::CreateTransmissionSemaphore(id, true)); + } + } + + void DeleteSemaphores() { + semaphores_.clear(); + } + + TSync_TimeStampType ts_; + TSync_VirtualLocalTimeType vt_; + TSync_UserDataType ud_; + TsyncIdMappingsHandler mappings_handler_; + std::vector semaphores_; + + void FillTimebaseIdMappings(void) { + for (auto it : id_mappings_map_) { + mappings_handler_.AddDomainMapping(it.second, it.first); + } + + mappings_handler_.CommitMappingsToSharedMemory(); + ASSERT_FALSE(mappings_handler_.IsEmpty()); + } + + static void WriteConfig(TSync_SynchronizedTimeBaseType timebase_id, TsyncTimeDomainConfig& cfg) { + auto writer = TimeBaseWriterFactory::Create( + std::find_if(id_mappings_map_.begin(), id_mappings_map_.end(), + [timebase_id](const auto& it) { return it.second == timebase_id; }) + ->first, + kSharedMemPageSize); + writer->GetAccessor().Open(); + std::lock_guard lock(*writer); + writer->Write(cfg); + } + +private: + static uint8_t shmem_buf0_[kSharedMemPageSize]; + static uint8_t shmem_buf1_[kSharedMemPageSize]; + static uint8_t shmem_buf2_[kSharedMemPageSize]; + static uint8_t shmem_buf_id_mappings[kIdMappingsShmemSize]; + + // mapping of timebase instance specifiers to timebase ids + using IdMappingsMap = std::map; + // mapping of shmem file names to file desriptors + using FileDescriptorMap = std::map; + // mapping of shmem file descriptors to buffers + using TimeBaseShMemMap = std::map; + + static IdMappingsMap id_mappings_map_; + static FileDescriptorMap file_descriptor_map_; + static TimeBaseShMemMap timebase_shmem_map_; +}; + +uint8_t PtpLibTestFixture::shmem_buf0_[kSharedMemPageSize]; +uint8_t PtpLibTestFixture::shmem_buf1_[kSharedMemPageSize]; +uint8_t PtpLibTestFixture::shmem_buf2_[kSharedMemPageSize]; +uint8_t PtpLibTestFixture::shmem_buf_id_mappings[kIdMappingsShmemSize]; + +PtpLibTestFixture::IdMappingsMap PtpLibTestFixture::id_mappings_map_ = { + {FAKE_INSTANCE_SPECIFIER_0, FAKE_TIMEBASE_ID_0}, + {FAKE_INSTANCE_SPECIFIER_1, FAKE_TIMEBASE_ID_1}, + {FAKE_INSTANCE_SPECIFIER_2, FAKE_TIMEBASE_ID_2}}; + +PtpLibTestFixture::FileDescriptorMap PtpLibTestFixture::file_descriptor_map_ = {{"/" FAKE_INSTANCE_SPECIFIER_0, 1}, + {"/" FAKE_INSTANCE_SPECIFIER_1, 2}, + {"/" FAKE_INSTANCE_SPECIFIER_2, 3}, + {kIdMappingsShmemFileName, 4}}; + +PtpLibTestFixture::TimeBaseShMemMap PtpLibTestFixture::timebase_shmem_map_ = { + {1, &shmem_buf0_[0]}, {2, &shmem_buf1_[0]}, {3, &shmem_buf2_[0]}, {4, &shmem_buf_id_mappings[0]}}; + +namespace testing { +namespace lib_tsyncptplib_ut { + +TEST_F(PtpLibTestFixture, Tsync_Open_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_Open_Twice_Succeeds) { + // no arrange + // act and assert (various steps) + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_CloseTimebase_AfterClose_Succeeds) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + TSync_Close(); + TSync_DB_Timebase_Meta_Data* md = static_cast(handle); + md->isInitialized = true; + // act + TSync_CloseTimebase(handle); +} + +TEST_F(PtpLibTestFixture, TSync_CloseTimeBase_WithInvalidHandle_Succeeds) { + // no arrange + // act and assert (various steps) + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto handle = TSync_OpenTimebase(99); + ASSERT_EQ(handle, TSYNC_INVALID_HANDLE); + TSync_CloseTimebase(handle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_CloseTimeBase_WithoutIdMappings_Succeeds) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + // act + TSync_Close(); + TSync_CloseTimebase(handle); + + // ASSERT + SUCCEED(); +} + +TEST_F(PtpLibTestFixture, TSync_OpenTimeBase_WithoutOpenCall_Fails) { + // no arrange + // act and assert + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_EQ(TSYNC_INVALID_HANDLE, handle); +} + +TEST_F(PtpLibTestFixture, TSync_OpenTimeBase_CalledTwice_Succeeds) { + // no arrange + // act and assert (various steps) + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + + auto handle2 = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_EQ(handle, handle2); + + TSync_CloseTimebase(handle); + TSync_CloseTimebase(handle2); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_OpenTimebase_WithInitializedHandleMetadata_Succeed) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timebase_meta_data = tsync_db_GetTimeBaseMetaDataElement(FAKE_TIMEBASE_ID_0); + timebase_meta_data->isInitialized.store(true); + // act + TSync_TimeBaseHandleType timeBaseHandle; + std::thread open_tb_thread([&timeBaseHandle]() { timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); }); + std::this_thread::sleep_for(std::chrono::seconds(2)); + timebase_meta_data->isInitialized.store(false); + open_tb_thread.join(); + // assert + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_OpenTimebase_WithInvalidTimeBaseId_Fails) { + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto h = TSync_OpenTimebase(99); + ASSERT_EQ(h, TSYNC_INVALID_HANDLE); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, Tsync_OpenTimebaseWithInvalidTimebaseId_Fails) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(TSYNC_INVALID_TIME_BASE_ID); + ASSERT_EQ(h, TSYNC_INVALID_HANDLE); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, Tsync_OpenTimebase_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + // need to inject a valid config for this to succeed + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, Tsync_OpenTimebase_ConsumerRole_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + // need to inject a valid config for this to succeed + for (uint16_t i = 0; i < TSYNC_DB_MAX_TIMEBASES; ++i) { + if (tsync_db_timebase_meta_data[i].mappingData.timeBaseId == FAKE_TIMEBASE_ID_0) { + tsync_db_timebase_meta_data[i].mappingData.timeBaseRole = TSYNC_DB_TIMEBASE_ROLE_CONSUMER; + break; + } + } + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, Tsync_OpenTimebase_WithoutSlash_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_1); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_SetTimeoutStatus_TimeoutBeforeSync_Succeeds) { + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + TSync_TimeBaseHandleType timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + TSync_TimeStampType ts; + TSync_VirtualLocalTimeType vt; + TSync_UserDataType ud; + + result = TSync_SetTimeoutStatus(timeBaseHandle); + ASSERT_EQ(result, E_OK); + + result = TSync_BusGetGlobalTime(timeBaseHandle, &ts, &ud, nullptr, &vt); + ASSERT_EQ(result, E_OK); + + // Timeout shall not be set as the first sync has not happened yet + TSync_TimeBaseStatusType status_expected = 0u; + + ASSERT_EQ(status_expected, ts.timeBaseStatus); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_WithInvalidHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + // act + result = TSync_BusSetGlobalTime(TSYNC_INVALID_HANDLE, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_WithUnInitializedHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + TSync_DB_Timebase_Meta_Data* metaData = static_cast(timeBaseHandle); + metaData->isInitialized.store(false); + // act + result = TSync_BusSetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_WithNullPtrParams_Succeeds) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + // act + result = TSync_BusSetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_OK); + + // clean-up + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_OnReadCurrentStatusFailure_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + score::time::SynchronizationStatus status{}; + EXPECT_CALL(*raw_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(status), Return(false))); + + // act + result = TSync_BusSetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + // clean-up + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_OnWriteTimeStampWithStatusFailure_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + writer_factory_mock_return_real_writer = false; + auto writer_mock = writer_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0,kSharedMemPageSize, false); + auto raw_writer_mock = static_cast(writer_mock.get()); + + reader_factory_mock_return_real_reader = false; + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_reader_mock = static_cast(reader_mock.get()); + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + EXPECT_CALL(*writer_factory_mock, Create(_, _, _)).WillOnce(Return(ByMove(std::move(writer_mock)))); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + score::time::SynchronizationStatus status{score::time::SynchronizationStatus::kSynchronized}; + EXPECT_CALL(*raw_reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(status), Return(true))); + EXPECT_CALL(*raw_writer_mock, Write(testing::An())).WillOnce(Return(false)); + + // act + TSync_TimeStampType ts; + result = TSync_BusSetGlobalTime(timeBaseHandle, &ts, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + // clean-up + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_WithTimeStampSkipFailure_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + writer_factory_mock_return_real_writer = false; + + auto writer_mock = writer_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize, false); + auto raw_mock = static_cast(writer_mock.get()); + + EXPECT_CALL(*writer_factory_mock, Create(_, _, _)).WillOnce(Return(ByMove(std::move(writer_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(true)); + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(false)); + + // act + result = TSync_BusSetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + // clean-up + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_WithLocalTimeSkipFailure_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + writer_factory_mock_return_real_writer = false; + + auto writer_mock = writer_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize, false); + auto raw_mock = static_cast(writer_mock.get()); + + EXPECT_CALL(*writer_factory_mock, Create(_, _, _)).WillOnce(Return(ByMove(std::move(writer_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(true)); + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(false)); + + // act + result = TSync_BusSetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + // clean-up + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTime_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + + result = TSync_BusSetGlobalTime(h, &ts_, nullptr, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 2; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 1; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 0; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 3; + + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusSetGlobalTimeWithSynctoGateWayStatus_Succeeds) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + EXPECT_CALL(*raw_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(score::time::SynchronizationStatus::kSynchToGateway), + Return(true))); + + // act + result = TSync_BusSetGlobalTime(timeBaseHandle, &ts_, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_OK); + + // clean-up + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithInvalidHandle_Fails) { + // no arrange + // act + TSync_ReturnType result = TSync_BusGetGlobalTime(TSYNC_INVALID_HANDLE, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithUnInitializedHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + TSync_DB_Timebase_Meta_Data* metaData = static_cast(timeBaseHandle); + metaData->isInitialized.store(false); + // act + result = TSync_BusGetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithNullPtrParams_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + result = TSync_BusGetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + ASSERT_EQ(result, E_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithTimeStampSkipFailure_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(true)); + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(false)); + + result = TSync_BusGetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + ASSERT_EQ(result, E_NOT_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithLocalTimeSkipFailure_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(true)); + EXPECT_CALL(*raw_mock, SetPosition(_)).WillOnce(Return(false)); + + result = TSync_BusGetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, nullptr); + ASSERT_EQ(result, E_NOT_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithTimeStampReadReturnFalse_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + TSync_TimeStampType ts; + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + EXPECT_CALL(*raw_mock, Read(testing::An())).WillOnce(Return(false)); + + result = TSync_BusGetGlobalTime(timeBaseHandle, &ts, nullptr, nullptr, nullptr); + ASSERT_EQ(result, E_NOT_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithVirtualLocalTimeReadReturnFalse_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + TSync_VirtualLocalTimeType vlt; + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + + EXPECT_CALL(*raw_mock, Read(testing::An())).WillOnce(Return(false)); + + result = TSync_BusGetGlobalTime(timeBaseHandle, nullptr, nullptr, nullptr, &vlt); + ASSERT_EQ(result, E_NOT_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithLUserDataReadReturnFalse_Fails) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + TSync_UserDataType ud; + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + + EXPECT_CALL(*raw_mock, Read(testing::An())).WillOnce(Return(false)); + + result = TSync_BusGetGlobalTime(timeBaseHandle, nullptr, &ud, nullptr, nullptr); + ASSERT_EQ(result, E_NOT_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_Succeeds) { + // no arrange + reader_factory_mock_return_real_reader = true; + writer_factory_mock_return_real_writer = true; + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + + TSync_TimeStampType ts; + TSync_VirtualLocalTimeType vt; + TSync_UserDataType ud; + + ud.userDataLength = 2; + + // Set the time values + ts.nanoseconds = 11; + ts.seconds = 22; + ts.secondsHi = 33; + ts.timeBaseStatus = 0; + + // act and assert (various steps) + ud_.userDataLength = 0; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + result = TSync_BusGetGlobalTime(h, &ts, &ud, nullptr, &vt); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 1; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + result = TSync_BusGetGlobalTime(h, &ts, &ud, nullptr, &vt); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 2; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + result = TSync_BusGetGlobalTime(h, &ts, &ud, nullptr, &vt); + ASSERT_EQ(result, E_OK); + + ud_.userDataLength = 3; + result = TSync_BusSetGlobalTime(h, &ts_, &ud_, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + result = TSync_BusGetGlobalTime(h, &ts, &ud, nullptr, &vt); + ASSERT_EQ(result, E_OK); + + // Sync status after first TSync_BusSetGlobalTime must be 8 + TSync_TimeBaseStatusType status_expected = 1 << TIMEBASE_STATUS_BIT_GLOBAL_SYNC; + + EXPECT_EQ(status_expected, ts.timeBaseStatus); + EXPECT_EQ(ts.seconds, ts_.seconds); + EXPECT_EQ(ts.secondsHi, ts_.secondsHi); + EXPECT_EQ(ts.nanoseconds, ts_.nanoseconds); + EXPECT_EQ(vt.nanosecondsHi, vt_.nanosecondsHi); + EXPECT_EQ(vt.nanosecondsLo, vt_.nanosecondsLo); + EXPECT_EQ(ud.userDataLength, ud_.userDataLength); + EXPECT_EQ(ud.userByte0, ud_.userByte0); + EXPECT_EQ(ud.userByte1, ud_.userByte1); + EXPECT_EQ(ud.userByte2, ud_.userByte2); + + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_BusGetGlobalTime_WithSynctoGatewayStatus_Succeeds) { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + score::time::TimestampWithStatus ts_read{score::time::SynchronizationStatus::kSynchToGateway, + std::chrono::nanoseconds(123), std::chrono::seconds(456)}; + + TSync_TimeStampType ts; + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + testing::InSequence seq; + + EXPECT_CALL(*raw_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(ts_read), Return(true))); + + result = TSync_BusGetGlobalTime(timeBaseHandle, &ts, nullptr, nullptr, nullptr); + ASSERT_EQ(result, E_OK); + + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_GetCurrentVirtualLocalTime_WithInvalidHandle_Fails) { + // no arrange + // act + TSync_ReturnType result = TSync_GetCurrentVirtualLocalTime(TSYNC_INVALID_HANDLE, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); +} + +TEST_F(PtpLibTestFixture, TSync_GetCurrentVirtualLocalTime_WithUnInitializedHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + TSync_DB_Timebase_Meta_Data* metaData = static_cast(timeBaseHandle); + metaData->isInitialized.store(false); + // act + TSync_VirtualLocalTimeType vt; + result = TSync_GetCurrentVirtualLocalTime(timeBaseHandle, &vt); + // assert + ASSERT_EQ(result, E_NOT_OK); + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_GetCurrentVirtualLocalTime_OnClockGettimeError_Fails) { + // arrange + EXPECT_CALL(*misc_mock, OsClockGetTime(_, _)).WillOnce(Return(-1)); + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + + TSync_VirtualLocalTimeType vt; + + result = TSync_GetCurrentVirtualLocalTime(h, &vt); + ASSERT_EQ(result, E_NOT_OK); + + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_GetCurrentVirtualLocalTime_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + + TSync_VirtualLocalTimeType vt; + + result = TSync_GetCurrentVirtualLocalTime(h, &vt); + ASSERT_EQ(result, E_OK); + + TSync_CloseTimebase(h); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_GetCurrentVirtualLocalTime_WithMultipleTimebase_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + + result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType h2 = TSync_OpenTimebase(FAKE_TIMEBASE_ID_1); + ASSERT_NE(h2, TSYNC_INVALID_HANDLE); + + TSync_VirtualLocalTimeType vt; + TSync_VirtualLocalTimeType vt2; + + result = TSync_GetCurrentVirtualLocalTime(h, &vt); + ASSERT_EQ(result, E_OK); + + result = TSync_GetCurrentVirtualLocalTime(h2, &vt2); + ASSERT_EQ(result, E_OK); + + TSync_CloseTimebase(h); + TSync_Close(); + + TSync_CloseTimebase(h2); + TSync_Close(); +} + +TSync_ReturnType ptpds_TransmitGlobalTime(TSync_SynchronizedTimeBaseType tb) { + (void)tb; + printf("\n%s\n", __func__); + return E_OK; +} + +TEST_F(PtpLibTestFixture, TSync_SetTimeoutStatus_TimeoutAfterSync_Succeeds) { + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + TSync_TimeStampType ts; + TSync_VirtualLocalTimeType vt; + TSync_UserDataType ud; + + // Call TSync_BusSetGlobalTime before setting timeout bit + result = TSync_BusSetGlobalTime(timeBaseHandle, &ts_, nullptr, nullptr, &vt_); + ASSERT_EQ(result, E_OK); + + result = TSync_SetTimeoutStatus(timeBaseHandle); + ASSERT_EQ(result, E_OK); + + result = TSync_BusGetGlobalTime(timeBaseHandle, &ts, &ud, nullptr, &vt); + ASSERT_EQ(result, E_OK); + + // Status has 2 bits set - Timeout bit and Sync bit - 0000 1001 + TSync_TimeBaseStatusType status_expected = (1 << TIMEBASE_STATUS_BIT_TIMEOUT); + + ASSERT_EQ(status_expected, ts.timeBaseStatus); +} + +TEST_F(PtpLibTestFixture, TSync_SetTimeoutStatus_WithInvalidhandle_Fails) { + TSync_ReturnType result = TSync_SetTimeoutStatus(TSYNC_INVALID_HANDLE); + ASSERT_EQ(result, E_NOT_OK); +} + +TEST_F(PtpLibTestFixture, TSync_TransmitGlobalTimeRunner_WithUnInitializedHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + TSync_DB_Timebase_Meta_Data* metaData = static_cast(timeBaseHandle); + metaData->isInitialized.store(false); + // act + auto resultPtr = TSync_TransmitGlobalTimeRunner(timeBaseHandle); + // assert + ASSERT_EQ(resultPtr, nullptr); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_TransmitGlobalTimeRunner_WithInvalidArguments_Fails) { + // arrange + ASSERT_EXIT( + { + // act + TSync_TransmitGlobalTimeRunner(nullptr); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +std::mutex transmission_mutex; +int ptpds_TransmitGlobalTimeCounter = 0; +TSync_ReturnType ptpds_TransmitGlobalTimeCounterFunction(TSync_SynchronizedTimeBaseType tb) { + (void)tb; + std::lock_guard lock(transmission_mutex); + ++ptpds_TransmitGlobalTimeCounter; + return E_OK; +} + +TEST_F(PtpLibTestFixture, TSync_RegisterTransmitGlobalTimeCallback_Succeeds) { + // no arrange + // act and assert (various steps) + auto result = TSync_Open(); + + ASSERT_EQ(result, E_OK); + auto h = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(h, TSYNC_INVALID_HANDLE); + + result = TSync_RegisterTransmitGlobalTimeCallback(h, ptpds_TransmitGlobalTime); + ASSERT_EQ(result, E_OK); + + result = TSync_RegisterTransmitGlobalTimeCallback(h, ptpds_TransmitGlobalTime); + ASSERT_EQ(result, E_OK); + + // compensates the time that transmitGlobalTimeRunner needs for spawning the transmitGlobalTimeRunner thread + // ptpd should be running and register the callback before a provider application + // triggers a transmission on the bus + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // signal transmission to wake the thread and trigger the callback + semaphores_[FAKE_TIMEBASE_ID_0].unlock(); + + // give the transmission thread a litte time to trigger the callback + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + result = TSync_RegisterTransmitGlobalTimeCallback(h, nullptr); + ASSERT_EQ(result, E_OK); + + result = TSync_RegisterTransmitGlobalTimeCallback(h, nullptr); // unregistering a callback twice is not possible + ASSERT_EQ(result, E_OK); + + TSync_CloseTimebase(h); + + // trigger thread cleanup with invalid handle for coverage + TSync_HandleCallBackThreadCleanup(TSYNC_INVALID_HANDLE); + + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_SetTimeoutStatus_WithUnInitializedHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + auto timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + TSync_DB_Timebase_Meta_Data* metaData = static_cast(timeBaseHandle); + metaData->isInitialized.store(false); + // act + result = TSync_SetTimeoutStatus(timeBaseHandle); + // assert + ASSERT_EQ(result, E_NOT_OK); + TSync_Close(); +} + +TSync_ReturnType Tsync_Test_TransmissionCallback(TSync_SynchronizedTimeBaseType) { + return E_OK; +} + +TEST_F(PtpLibTestFixture, TSync_CloseTwice_Succeeds) { + // no arrange + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + TSync_Close(); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_GetTimebaseConfiguration_Succeeds) { + // arrange: open lib and timebase + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + // use mocked reader so we can inject arbitrary configurations + TSync_TimeBaseHandleType timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + // arrange: config (this will happen multiple times to get coverage for all good + // code paths) + TsyncTimeDomainConfig cfg; + TSync_Role role_requested; + + role_requested = TSYNC_ROLE_MASTER; + cfg.consumer_config.time_slave_config.is_valid = true; + cfg.provider_config.time_master_config.is_valid = true; + cfg.provider_config.time_master_config.sub_tlv_config.user_data_enabled = true; + cfg.provider_config.time_master_config.sub_tlv_config.status_enabled = true; + cfg.provider_config.time_master_config.sub_tlv_config.time_enabled = true; + cfg.provider_config.time_master_config.crc_support = TsyncCrcSupport::kSupported; + cfg.consumer_config.time_slave_config.crc_validation = TsyncCrcValidation::kIgnored; + cfg.eth_time_domain.message_format = TsyncEthMessageFormat::kIeee8021AS; + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + + // act: read config (repeated with different configs) + TSync_TimeBaseConfiguration config; + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(result, E_OK); + ASSERT_EQ(config.crcSupport, TSYNC_CRC_SUPPORTED); + ASSERT_EQ(config.crcFlags.correction_field, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcFlags.domain_number, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcFlags.message_length, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcFlags.precise_origin_timestamp, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcFlags.sequence_id, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcFlags.source_port_identity, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcValidation, TSYNC_CRC_IGNORED); + ASSERT_EQ(config.messageCompliance, TSYNC_MC_IEEE8021AS); + ASSERT_EQ(config.role, TSYNC_ROLE_MASTER); + ASSERT_EQ(config.subTlvConfig.followUpUserDataSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpStatusSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpTimeSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + // arrange + cfg.provider_config.time_master_config.crc_support = TsyncCrcSupport::kNotSupported; + cfg.eth_time_domain.crcFlags.correction_field = TSYNC_CRC_SUPPORTED; + cfg.eth_time_domain.crcFlags.domain_number = TSYNC_CRC_SUPPORTED; + cfg.eth_time_domain.crcFlags.message_length = TSYNC_CRC_SUPPORTED; + cfg.eth_time_domain.crcFlags.precise_origin_timestamp = TSYNC_CRC_SUPPORTED; + cfg.eth_time_domain.crcFlags.sequence_id = TSYNC_CRC_SUPPORTED; + cfg.eth_time_domain.crcFlags.source_port_identity = TSYNC_CRC_SUPPORTED; + cfg.consumer_config.time_slave_config.crc_validation = TsyncCrcValidation::kNotValidated; + cfg.provider_config.time_master_config.sub_tlv_config.user_data_enabled = false; + cfg.provider_config.time_master_config.sub_tlv_config.status_enabled = false; + cfg.provider_config.time_master_config.sub_tlv_config.time_enabled = false; + cfg.eth_time_domain.message_format = TsyncEthMessageFormat::kIeee8021ASAutosar; + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(result, E_OK); + ASSERT_EQ(config.crcSupport, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcFlags.correction_field, TSYNC_CRC_SUPPORTED); + ASSERT_EQ(config.crcFlags.domain_number, TSYNC_CRC_SUPPORTED); + ASSERT_EQ(config.crcFlags.message_length, TSYNC_CRC_SUPPORTED); + ASSERT_EQ(config.crcFlags.precise_origin_timestamp, TSYNC_CRC_SUPPORTED); + ASSERT_EQ(config.crcFlags.sequence_id, TSYNC_CRC_SUPPORTED); + ASSERT_EQ(config.crcFlags.source_port_identity, TSYNC_CRC_SUPPORTED); + + ASSERT_EQ(config.crcValidation, TSYNC_CRC_NOT_VALIDATED); + ASSERT_EQ(config.messageCompliance, TSYNC_MC_IEEE8021AS_AUTOSAR); + ASSERT_EQ(config.subTlvConfig.followUpUserDataSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpStatusSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpTimeSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + + // arrange + cfg.provider_config.time_master_config.crc_support = static_cast(13); // invalid + cfg.consumer_config.time_slave_config.crc_validation = TsyncCrcValidation::kOptional; + cfg.eth_time_domain.message_format = static_cast(13); // invalid + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(result, E_OK); + ASSERT_EQ(config.crcSupport, TSYNC_CRC_NOT_SUPPORTED); + ASSERT_EQ(config.crcValidation, TSYNC_CRC_OPTIONAL); + ASSERT_EQ(config.messageCompliance, TSYNC_MC_IEEE8021AS_AUTOSAR); + // arrange + cfg.consumer_config.time_slave_config.crc_validation = TsyncCrcValidation::kValidated; + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(config.crcValidation, TSYNC_CRC_VALIDATED); + // arrange + cfg.consumer_config.time_slave_config.crc_validation = static_cast(13); // invalid + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(config.crcValidation, TSYNC_CRC_OPTIONAL); + // arrange + cfg.eth_time_domain.num_fup_data_id_entries = TSYNC_MAX_FUP_DATA_ID_ENTRIES; + for (int32_t i = 0; i < TSYNC_MAX_FUP_DATA_ID_ENTRIES; ++i) { + cfg.eth_time_domain.fup_data_id_list[i] = static_cast(i); + } + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(config.numFupDataIdEntries, cfg.eth_time_domain.num_fup_data_id_entries); + for (int32_t i = 0; i < TSYNC_MAX_FUP_DATA_ID_ENTRIES; ++i) { + ASSERT_EQ(config.fupDataIdList[i], cfg.eth_time_domain.fup_data_id_list[i]); + } + // arrange + role_requested = TSYNC_ROLE_SLAVE; + cfg.provider_config.time_master_config.is_valid = false; + cfg.consumer_config.time_slave_config.sub_tlv_config.status_enabled = true; + cfg.consumer_config.time_slave_config.sub_tlv_config.time_enabled = true; + cfg.consumer_config.time_slave_config.sub_tlv_config.user_data_enabled = true; + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(config.role, TSYNC_ROLE_SLAVE); + ASSERT_EQ(config.subTlvConfig.followUpStatusSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpTimeSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpUserDataSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + + // arrange + cfg.consumer_config.time_slave_config.sub_tlv_config.status_enabled = false; + cfg.consumer_config.time_slave_config.sub_tlv_config.time_enabled = false; + cfg.consumer_config.time_slave_config.sub_tlv_config.user_data_enabled = false; + WriteConfig(FAKE_TIMEBASE_ID_0, cfg); + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + // assert + ASSERT_EQ(config.role, TSYNC_ROLE_SLAVE); + ASSERT_EQ(config.subTlvConfig.followUpStatusSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpTimeSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpUserDataSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + + // arrange + role_requested = TSYNC_ROLE_INVALID; + // act + result = TSync_GetTimebaseConfiguration(timeBaseHandle, role_requested, &config); + ASSERT_EQ(result, E_OK); + ASSERT_EQ(config.subTlvConfig.followUpStatusSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpTimeSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + ASSERT_EQ(config.subTlvConfig.followUpUserDataSubTLV, TSYNC_SUBTLV_NOT_SUPPORTED); + + // cleanup + TSync_CloseTimebase(timeBaseHandle); + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_GetTimebaseConfigurationWithInvalidHandle_Fails) { + // arrange + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(E_OK, result); + // act + result = TSync_GetTimebaseConfiguration(TSYNC_INVALID_HANDLE, TSYNC_ROLE_MASTER, nullptr); + // assert + ASSERT_EQ(result, E_NOT_OK); + // cleanup + TSync_Close(); +} + +TEST_F(PtpLibTestFixture, TSync_Open_With16ElementsInMapping_OK) { + // arrange + + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + uint32_t num_zero{0}; + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + EXPECT_CALL(*raw_mock, Open()).Times(1); + EXPECT_CALL(*raw_mock, lock()).Times(1); + EXPECT_CALL(*raw_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kNumTimeDomains), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[0]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[1]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[2]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[3]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[4]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[5]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[6]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[7]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[8]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[9]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[10]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[11]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[12]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[13]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[14]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainIds[15]), Return(true))) + .WillRepeatedly(::testing::DoAll(testing::SetArgReferee<0>(num_zero), Return(true))); + EXPECT_CALL(*raw_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[0]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[1]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[2]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[3]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[4]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[5]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[6]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[7]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[8]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[9]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[10]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[11]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[12]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[13]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[14]), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[15]), Return(true))); + + EXPECT_CALL(*raw_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kConsumerMagic_), Return(true))) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kProviderMagic_), Return(true))); + + EXPECT_CALL(*raw_mock, unlock()).Times(1); + + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); +} + +TEST_F(PtpLibTestFixture, TSync_HandleCallBackThreadCleanup_threadStatusIsDead_Succeeds) { + // Arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_DB_Timebase_Meta_Data timeBaseMeta; + timeBaseMeta.transmitGlobalTime.threadStatus.store(TSYNC_DB_THREAD_STATUS_DEAD); + TSync_TimeBaseHandleType timeBaseHandle = static_cast(&timeBaseMeta); + + // Act + TSync_HandleCallBackThreadCleanup(timeBaseHandle); + TSync_Close(); +} + +using PtpLibDeathTestFixture = PtpLibTestFixture; + +TEST_F(PtpLibDeathTestFixture, TSync_Open_WithoutIDMappings_Aborts) { + ASSERT_EXIT( + { + // arrange + InvalidateTimeBaseMappings(); + // act + TSync_Open(); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, Tsync_Open_SharedMemOpenFails_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*shared_mem_mock, OsShmOpen(_, _, _)).WillOnce(Return(-1)); + // act + (void)TSync_Open(); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, Tsync_Open_MmapUnavailable_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*shared_mem_mock, OsMmap(_, _, _, _, _, _)).WillRepeatedly(Return(MAP_FAILED)); + // act + (void)TSync_Open(); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, TSync_OpenTimeBase_OnConfigReadError_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + EXPECT_CALL(*raw_mock, Read(An())).WillOnce(Return(false)); + + // act + (void)TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("could not read config of time domain")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_CloseTimeBase_WithTransmissionThreadNotDead_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + + // act + TSync_DB_Timebase_Meta_Data* md = static_cast(handle); + md->transmitGlobalTime.threadStatus.store(TSYNC_DB_THREAD_STATUS_ALIVE); + tsync_db_CloseTimebase(md); + }, + ::testing::ExitedWithCode(ABORT_CODE), + HasSubstr("transmitGlobalTime.threadStatus != TSYNC_DB_THREAD_STATUS_DEAD")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_CloseTimeBase_WithTransmissionThreadSignalNotNone_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + + // act + TSync_DB_Timebase_Meta_Data* md = static_cast(handle); + md->transmitGlobalTime.threadSignal.store(TSYNC_DB_THREAD_SIGNAL_CONTINUE); + tsync_db_CloseTimebase(md); + }, + ::testing::ExitedWithCode(ABORT_CODE), + HasSubstr("failed transmitGlobalTime.threadSignal != TSYNC_DB_THREAD_SIGNAL_NONE")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_CloseTimeBase_WithGetDomainNameFails_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + + // act + TSync_DB_Timebase_Meta_Data* md = static_cast(handle); + md->mappingData.timeBaseId = 0xff; + md->transmitGlobalTime.threadSignal.store(TSYNC_DB_THREAD_SIGNAL_CONTINUE); + + tsync_db_CloseTimebase(md); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("could not determine domain name for timebase")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_CloseTimeBase_WithTransmissionCallbackNotNull_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + + // act + TSync_DB_Timebase_Meta_Data* md = static_cast(handle); + md->transmitGlobalTime.callback = &Tsync_Test_TransmissionCallback; + tsync_db_CloseTimebase(md); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("callback != nullptr")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_RegisterTransmitGlobalTimeCallback_OnSemPostFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*named_semaphore_mock, OsSemPost(_)).WillOnce(Return(-1)); + + // act and assert + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + result = TSync_RegisterTransmitGlobalTimeCallback(timeBaseHandle, ptpds_TransmitGlobalTime); + ASSERT_EQ(result, E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + TSync_CloseTimebase(timeBaseHandle); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, TSync_RegisterTransmitGlobalTimeCallback_OnThreadCreateFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*misc_mock, OsThreadCreate(_, _, _, _)).WillOnce(Return(-1)); + + // act and assert + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + result = TSync_RegisterTransmitGlobalTimeCallback(timeBaseHandle, ptpds_TransmitGlobalTime); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, TSync_RegisterTransmitGlobalTimeCallback_OnThreadJoinFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*misc_mock, OsThreadJoin(_, _)).WillOnce(Return(-1)); + + // act and assert + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TSync_TimeBaseHandleType timeBaseHandle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(timeBaseHandle, TSYNC_INVALID_HANDLE); + + result = TSync_RegisterTransmitGlobalTimeCallback(timeBaseHandle, ptpds_TransmitGlobalTime); + ASSERT_EQ(result, E_OK); + + TSync_CloseTimebase(timeBaseHandle); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, TSync_OpenTimebase_OnInvalidTimeBaseReader_Aborts) { + ASSERT_EXIT( + { + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + // use fake reader factory mock to allow the creation of the reader to fail. + reader_factory_mock_return_real_reader = false; + EXPECT_CALL(*reader_factory_mock, Create(_, _)) + .WillOnce(Return(ByMove(std::unique_ptr(nullptr)))); + (void)TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("could not open reader for time domain")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_OpenTimebase_OnInvalidConfig_Aborts) { + ASSERT_EXIT( + { + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + TsyncTimeDomainConfig cfg; + cfg.consumer_config.time_slave_config.is_valid = false; + cfg.provider_config.time_master_config.is_valid = false; + // use fake reader to return fake config + reader_factory_mock_return_real_reader = false; + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + EXPECT_CALL(*raw_mock, Read(An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(cfg), Return(true))); + + (void)TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("has neither a valid provider nor consumer configuration")); +} + +TEST_F(PtpLibDeathTestFixture, Tsync_OpenTimebase_OsShmOpenFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*shared_mem_mock, OsShmOpen(HasSubstr(kIdMappingsShmemFileName), _, _)) + .WillOnce(::testing::DoDefault()); + EXPECT_CALL(*shared_mem_mock, OsShmOpen(HasSubstr(FAKE_INSTANCE_SPECIFIER_0), _, _)).WillOnce(Return(-1)); + + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + (void)TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, Tsync_OpenTimebase_OsMmapFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*shared_mem_mock, OsMmap(_, _, _, _, _, _)) + .WillOnce(::testing::DoDefault()) + .WillOnce(Return(MAP_FAILED)); + // act and assert (various steps) + TSync_ReturnType result = TSync_Open(); + ASSERT_EQ(result, E_OK); + (void)TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + }, + ::testing::ExitedWithCode(ABORT_CODE), ".*"); +} + +TEST_F(PtpLibDeathTestFixture, TSync_GetTimebaseConfiguration_OnReadDomainConfigError_Aborts) { + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + // use mocked reader + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + + ASSERT_EXIT( + { + // arrange + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + EXPECT_CALL(*raw_mock, Read(An())).WillOnce(Return(false)); + + // act + TSync_TimeBaseConfiguration cfg; + TSync_Role role_requested = TSYNC_ROLE_MASTER; // Any role for now + (void)TSync_GetTimebaseConfiguration(handle, role_requested, &cfg); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("Could not read time domain configuration")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_SetTimeoutStatus_OnTimeStampReadError_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + // use mocked reader + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + EXPECT_CALL(*raw_mock, Read(An())).WillOnce(Return(false)); + (void)TSync_SetTimeoutStatus(handle); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("could not read timestamp")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_GetTimebaseConfiguration_OnInvalidConfig_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + + // use mocked reader + reader_factory_mock_return_real_reader = false; + + auto reader_mock = reader_factory_mock->Create(FAKE_INSTANCE_SPECIFIER_0, kSharedMemPageSize); + auto raw_mock = static_cast(reader_mock.get()); + EXPECT_CALL(*reader_factory_mock, Create(_, _)).WillOnce(Return(ByMove(std::move(reader_mock)))); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + + TsyncTimeDomainConfig domain_cfg; + + domain_cfg.consumer_config.time_slave_config.is_valid = false; + domain_cfg.provider_config.time_master_config.is_valid = false; + + EXPECT_CALL(*raw_mock, Read(An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_cfg), Return(true))); + + // act + TSync_TimeBaseConfiguration cfg; + TSync_Role role_requested = TSYNC_ROLE_MASTER; // Any role for now + (void)TSync_GetTimebaseConfiguration(handle, role_requested, &cfg); + }, + ::testing::ExitedWithCode(ABORT_CODE), + HasSubstr("Neither the time-consumer config nor the time-provider config is valid")); +} + +TEST_F(PtpLibDeathTestFixture, TSync_CloseTimebase_OnNegativeRefCount_Aborts) { + ASSERT_EXIT( + { + // arrange + auto result = TSync_Open(); + ASSERT_EQ(result, E_OK); + auto handle = TSync_OpenTimebase(FAKE_TIMEBASE_ID_0); + ASSERT_NE(handle, TSYNC_INVALID_HANDLE); + TSync_DB_Timebase_Meta_Data* meta_data = static_cast(handle); + meta_data->refCounter = 0; + // act + TSync_CloseTimebase(handle); + }, + ::testing::ExitedWithCode(ABORT_CODE), HasSubstr("tsync_db_CloseTimebase - refCounter is negative")); +} + +} // namespace lib_tsyncptplib_ut +} // namespace testing diff --git a/src/tsync-utility-lib/BUILD b/src/tsync-utility-lib/BUILD new file mode 100644 index 0000000..2299623 --- /dev/null +++ b/src/tsync-utility-lib/BUILD @@ -0,0 +1,175 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "tsync_utility_if", + hdrs = glob([ + "include/**/*.h" + ]), + strip_include_prefix = "include", + deps = [ + "//src/tsync-lib:tsync_if", + "@score_baselibs//score/language/futurecpp:futurecpp", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "utility_shared_mem_time_base_reader", + srcs = [ + "src/SharedMemTimeBaseReader.cpp", + ], + hdrs = [ + "src/SharedMemLayout.h", + "src/SharedMemTimeBaseReader.h", + "src/TsyncReadWriteLock.h", + ], + strip_include_prefix = "src", + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_shared_mem_time_base_writer", + srcs = [ + "src/SharedMemTimeBaseWriter.cpp", + ], + hdrs = [ + "src/SharedMemLayout.h", + "src/SharedMemTimeBaseWriter.h", + "src/TsyncReadWriteLock.h", + ], + strip_include_prefix = "src", + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_sys_calls", + srcs = ["src/SysCalls.cpp"], + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_time_base_reader_factory", + srcs = [ + "src/SharedMemTimeBaseReader.h", + "src/TimeBaseReaderFactory.cpp", + "src/TsyncReadWriteLock.h", + ], + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_time_base_writer_factory", + srcs = [ + "src/SharedMemTimeBaseWriter.h", + "src/TimeBaseWriterFactory.cpp", + "src/TsyncReadWriteLock.h", + ], + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_tsync_id_mappings_handler", + srcs = ["src/TsyncIdMappingsHandler.cpp"], + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_tsync_named_semaphore", + srcs = ["src/TsyncNamedSemaphore.cpp"], + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_tsync_read_write_lock", + srcs = ["src/TsyncReadWriteLock.cpp"], + hdrs = ["src/TsyncReadWriteLock.h"], + strip_include_prefix = "src", + deps = [ + ":tsync_utility_if", + "//src/common", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_tsync_shared_utils", + srcs = ["src/TsyncSharedUtils.cpp"], + deps = [ + ":tsync_utility_if", + "//src/common", + "@score_baselibs//score/language/futurecpp", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "tsync_utility", + hdrs = [ + ":tsync_utility_if", + ], + srcs = [ + ":utility_tsync_id_mappings_handler", + ":utility_time_base_reader_factory", + ":utility_time_base_writer_factory", + ":utility_shared_mem_time_base_reader", + ":utility_shared_mem_time_base_writer", + ":utility_tsync_shared_utils", + ":utility_tsync_named_semaphore", + ":utility_tsync_read_write_lock", + ":utility_sys_calls", + ], + deps = [ + ":tsync_utility_if", + "//src/common", + ], + # linkstatic = True, + visibility = [ + "//src:__subpackages__", + ], +) diff --git a/src/tsync-utility-lib/include/score/time/utility/ITimeBaseAccessor.h b/src/tsync-utility-lib/include/score/time/utility/ITimeBaseAccessor.h new file mode 100644 index 0000000..3869d05 --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/ITimeBaseAccessor.h @@ -0,0 +1,69 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_ITIMEBASEACCESSOR_H_ +#define SCORE_TIME_UTILITY_ITIMEBASEACCESSOR_H_ + +#include +#include +#include +#include + +#include "score/time/utility/TsyncSharedUtils.h" + +namespace score { +namespace time { + +class ITimeBaseAccessor { +public: + enum class State : std::uint32_t { Open, Closed }; + + virtual ~ITimeBaseAccessor() noexcept = default; + + virtual void Open() /* noexcept */ = 0; + virtual void Close() /* noexcept */ = 0; + virtual State GetState() const /* noexcept */ = 0; + virtual std::string_view GetName() const /* noexcept */ = 0; +}; + + +class ITimeBaseReader; +class ITimeBaseWriter; + +template +typename std::enable_if<(std::is_base_of::value || + std::is_base_of::value), + bool>::type +Align(ReaderWriterType& a) { + std::size_t pos{a.GetPosition()}; + const std::size_t alignment_mod{pos % alignof(FIELDTYPE)}; + if (alignment_mod != 0) { + if (SafeAdd(pos, (alignof(FIELDTYPE) - alignment_mod), a.GetMaxSize() - 1)) { + return a.SetPosition(pos); + } else { + return false; + } + } + + return true; +} + +template +typename std::enable_if<(std::is_base_of::value || + std::is_base_of::value), + bool>::type +AlignedSkip(ReaderWriterType& a) { + std::size_t pos = a.GetPosition(); + + if (SafeAdd(pos, sizeof(FIELDTYPE), a.GetMaxSize() - 1)) { + return (a.SetPosition(pos) && Align(a)); + } + + return false; +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_ITIMEBASEACCESSOR_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/ITimeBaseReader.h b/src/tsync-utility-lib/include/score/time/utility/ITimeBaseReader.h new file mode 100644 index 0000000..dbca872 --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/ITimeBaseReader.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_ITIMEBASEREADER_H_ +#define SCORE_TIME_UTILITY_ITIMEBASEREADER_H_ + +#include +#include +#include +#include + +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/TsyncConfigTypes.h" + +namespace score { +namespace time { + +class ITimeBaseReader { +public: + virtual ~ITimeBaseReader() noexcept = default; + + virtual ITimeBaseAccessor& GetAccessor() /* noexcept */ = 0; + + virtual bool Read(std::uint8_t& data) /* noexcept */ = 0; + virtual bool Read(std::uint16_t& data) /* noexcept */ = 0; + virtual bool Read(std::uint32_t& data) /* noexcept */ = 0; + virtual bool Read(std::uint64_t& data) /* noexcept */ = 0; + virtual bool Read(std::int8_t& data) /* noexcept */ = 0; + virtual bool Read(std::int16_t& data) /* noexcept */ = 0; + virtual bool Read(std::int32_t& data) /* noexcept */ = 0; + virtual bool Read(std::int64_t& data) /* noexcept */ = 0; + // these may not be portable + virtual bool Read(float& data) /* noexcept */ = 0; + virtual bool Read(double& data) /* noexcept */ = 0; + + virtual bool Read(std::string& data) /* noexcept */ = 0; + // the std::string_view instance will become invalid when the reader is closed. + virtual bool Read(std::string_view& data) /* noexcept */ = 0; + + // Function to remove dependency on ptp-lib + virtual bool Read(TimestampWithStatus& data) /* noexcept */ = 0; + virtual bool Read(VirtualLocalTime& data) /* noexcept */ = 0; + virtual bool Read(UserDataView& data) /* noexcept */ = 0; + virtual bool Read(SynchronizationStatus& data) /* noexcept */ = 0; + + // zero-copy read, performance vs. type-safety + virtual bool Read(const char** p, std::size_t numBytes) /* noexcept */ = 0; + + virtual bool Read(TsyncTimeDomainConfig& data) /* noexcept */ = 0; + virtual bool Read(TsyncConsumerConfig& data) /* noexcept */ = 0; + virtual bool Read(TsyncProviderConfig& data) /* noexcept */ = 0; + + virtual bool Skip(std::size_t numBytes) /* noexcept */ = 0; + virtual bool SetPosition(std::size_t pos) /* noexcept */ = 0; + virtual std::size_t GetPosition() const /* noexcept */ = 0; + virtual std::size_t GetMaxSize() const /* noexcept */ = 0; + + // std::BasicLockable requirements + virtual void lock() /* noexcept */ = 0; + virtual void unlock() /* noexcept */ = 0; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_ITIMEBASEREADER_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/ITimeBaseWriter.h b/src/tsync-utility-lib/include/score/time/utility/ITimeBaseWriter.h new file mode 100644 index 0000000..f7c58ce --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/ITimeBaseWriter.h @@ -0,0 +1,72 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_ITIMEBASEWRITER_H_ +#define SCORE_TIME_UTILITY_ITIMEBASEWRITER_H_ + +#include +#include +#include +#include + +#include "score/span.hpp" + +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/TsyncConfigTypes.h" + +namespace score { +namespace time { + +struct TsyncConsumerConfig; +struct TsyncProviderConfig; + +class ITimeBaseWriter { +public: + virtual ~ITimeBaseWriter() noexcept = default; + + virtual ITimeBaseAccessor& GetAccessor() /* noexcept */ = 0; + + virtual bool Write(std::uint8_t data) /* noexcept */ = 0; + virtual bool Write(std::uint16_t data) /* noexcept */ = 0; + virtual bool Write(std::uint32_t data) /* noexcept */ = 0; + virtual bool Write(std::uint64_t data) /* noexcept */ = 0; + virtual bool Write(std::int8_t data) /* noexcept */ = 0; + virtual bool Write(std::int16_t data) /* noexcept */ = 0; + virtual bool Write(std::int32_t data) /* noexcept */ = 0; + virtual bool Write(std::int64_t data) /* noexcept */ = 0; + virtual bool Write(const std::string& data) /* noexcept */ = 0; + virtual bool Write(std::string_view data) /* noexcept */ = 0; + virtual bool Write(score::cpp::span data) /* noexcept */ = 0; + + // Function to remove dependency on ptp-lib + virtual bool Write(const TimestampWithStatus& data) /* noexcept */ = 0; + virtual bool Write(const VirtualLocalTime& data) /* noexcept */ = 0; + virtual bool Write(UserData data) /* noexcept */ = 0; + virtual bool Write(UserDataView data) /* noexcept */ = 0; + + virtual bool Write(const SynchronizationStatus& data) /* noexcept */ = 0; + // these may not be portable + virtual bool Write(float data) /* noexcept */ = 0; + virtual bool Write(double data) /* noexcept */ = 0; + + virtual bool Write(const TsyncTimeDomainConfig& data) /* noexcept */ = 0; + virtual bool Write(const TsyncConsumerConfig& data) /* noexcept */ = 0; + virtual bool Write(const TsyncProviderConfig& data) /* noexcept */ = 0; + virtual bool Write(const char* data, std::size_t size) /* noexcept */ = 0; + + virtual bool WriteDefaults() /* noexcept */ = 0; + virtual bool Skip(std::size_t num_bytes) /* noexcept */ = 0; + virtual bool SetPosition(std::size_t pos) /* noexcept */ = 0; + virtual std::size_t GetPosition() const /* noexcept */ = 0; + virtual std::size_t GetMaxSize() const /* noexcept */ = 0; + + // std::BasicLockable requirements + virtual void lock() /* noexcept */ = 0; + virtual void unlock() /* noexcept */ = 0; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_ITIMEBASEWRITER_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/SysCalls.h b/src/tsync-utility-lib/include/score/time/utility/SysCalls.h new file mode 100644 index 0000000..7896722 --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/SysCalls.h @@ -0,0 +1,49 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_SYSCALLS_H_ +#define SCORE_TIME_UTILITY_SYSCALLS_H_ + +#include +#include +#include +#include + +namespace score { +namespace time { + +int OsShmOpen(const char* name, int oflag, mode_t mode); +int OsShmUnlink(const char* name); +int OsFtruncate(int fd, off_t length); +void* OsMmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset); +int OsMunmap(void* addr, size_t len); +int OsClose(int fd); +mode_t OsUmask(mode_t mode); +sem_t* OsSemOpen(const char* name, int oflag); +sem_t* OsSemOpen(const char* name, int oflag, mode_t mode, unsigned int value); +int OsSemClose(sem_t* sem); +int OsSemUnlink(const char* name); +int OsSemWait(sem_t* sem); +int OsSemPost(sem_t* sem); +int OsSemTryWait(sem_t* sem); +int OsSemGetValue(sem_t* sem, int* value); +int OsRwLockInitAttr(pthread_rwlockattr_t* attr); +int OsRwLockDestroyAttr(pthread_rwlockattr_t* attr); +int OsRwLockAttrSetPShared(pthread_rwlockattr_t* attr, int shared); +int OsRwLockInit(pthread_rwlock_t* rw_lock, const pthread_rwlockattr_t* attr); +int OsRwLockTryReadLock(pthread_rwlock_t* rw_lock); +int OsRwLockReadLock(pthread_rwlock_t* rw_lock); +int OsRwLockTryWriteLock(pthread_rwlock_t* rw_lock); +int OsRwLockWriteLock(pthread_rwlock_t* rw_lock); +int OsRwLockUnlock(pthread_rwlock_t* rw_lock); +int OsRwLockDestroyLock(pthread_rwlock_t* rw_lock); +int OsClockGetTime(clockid_t clk_id, timespec* tp); +int OsThreadCreate(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg); +int OsThreadJoin(pthread_t thread, void** retval); +mode_t OsUmask(mode_t mode); + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_SYSCALLS_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/TimeBaseReaderFactory.h b/src/tsync-utility-lib/include/score/time/utility/TimeBaseReaderFactory.h new file mode 100644 index 0000000..47ed18b --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/TimeBaseReaderFactory.h @@ -0,0 +1,28 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TIMEBASEREADERFACTORY_H_ +#define SCORE_TIME_UTILITY_TIMEBASEREADERFACTORY_H_ + +#include +#include +#include + +#include "score/time/utility/ITimeBaseReader.h" + +namespace score { +namespace time { + +class TimeBaseReaderFactory { +public: + using PointerType = std::unique_ptr; + + static PointerType Create(std::string_view name); + static PointerType Create(std::string_view name, std::size_t max_size); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TIMEBASEREADERFACTORY_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/TimeBaseWriterFactory.h b/src/tsync-utility-lib/include/score/time/utility/TimeBaseWriterFactory.h new file mode 100644 index 0000000..e61d7ca --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/TimeBaseWriterFactory.h @@ -0,0 +1,28 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TIMEBASEWRITERFACTORY_H_ +#define SCORE_TIME_UTILITY_TIMEBASEWRITERFACTORY_H_ + +#include +#include +#include + +#include "score/time/utility/ITimeBaseWriter.h" + +namespace score { +namespace time { + +class TimeBaseWriterFactory { +public: + using PointerType = std::unique_ptr; + + static PointerType Create(std::string_view name, bool is_owner = false); + static PointerType Create(std::string_view name, std::size_t max_size, bool is_owner = false); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TIMEBASEWRITERFACTORY_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/TsyncConfigTypes.h b/src/tsync-utility-lib/include/score/time/utility/TsyncConfigTypes.h new file mode 100644 index 0000000..352cbfc --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/TsyncConfigTypes.h @@ -0,0 +1,109 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TSYNCCONFIGTYPES_H_ +#define SCORE_TIME_UTILITY_TSYNCCONFIGTYPES_H_ + +#include +#include +#include + +namespace score { +namespace time { + +// Note: +// Ecu-Config uses the unit seconds for most time values given as a +// floating point value. To accomodate this, milliseconds are used below +// to be able to work with fractions of a second. The actual represenation of +// these values is a int64_t. + +// coverity[autosar_cpp14_a0_1_1_violation:FALSE] #49860: This value is used subsequently. +constexpr std::size_t kMacAddressStringLength = 12U; +// coverity[autosar_cpp14_a0_1_1_violation:FALSE] #49860: This value is used subsequently. +constexpr std::uint32_t kMaxFupDataIdEntries = 16U; + +enum class TsyncCrcSupport : uint32_t { kSupported = 0U, kNotSupported = 1U }; + +enum class TsyncCrcValidation : uint32_t { kValidated = 0U, kNotValidated = 1U, kIgnored = 2U, kOptional = 3U }; + +enum class TsyncEthMessageFormat : uint32_t { kIeee8021AS = 0U, kIeee8021ASAutosar = 1U }; + +struct TsyncCrcFlags { + bool correction_field = false; + bool domain_number = false; + bool message_length = false; + bool precise_origin_timestamp = false; + bool sequence_id = false; + bool source_port_identity = false; +}; + +struct TsyncEthTimeDomain { + uint8_t fup_data_id_list[kMaxFupDataIdEntries]{}; + char dest_mac_address[kMacAddressStringLength + 1U]{}; + TsyncEthMessageFormat message_format = TsyncEthMessageFormat::kIeee8021ASAutosar; + uint32_t vlan_priority = 0U; + uint32_t num_fup_data_id_entries = 0U; + TsyncCrcFlags crcFlags{}; + bool is_valid = false; // set to true when the config values have been initialized +}; + +struct TsyncSubTlvConfig { + bool time_enabled = false; + bool status_enabled = false; + bool user_data_enabled = false; +}; + +struct TsyncTimeMaster { + std::chrono::milliseconds immediate_resume_time = std::chrono::milliseconds::zero(); + std::chrono::milliseconds sync_period = std::chrono::milliseconds::zero(); + TsyncCrcSupport crc_support = TsyncCrcSupport::kNotSupported; + bool is_system_wide_global_time_master = false; + TsyncSubTlvConfig sub_tlv_config{}; + bool is_valid = false; // set to true when the config values have been initialized +}; + +struct TsyncTimeSlave { + std::chrono::milliseconds follow_up_timeout_value = std::chrono::milliseconds::zero(); + std::chrono::milliseconds time_leap_future_threshold = std::chrono::milliseconds::zero(); + std::chrono::milliseconds time_leap_past_threshold = std::chrono::milliseconds::zero(); + std::uint32_t time_leap_healing_counter = 0; + TsyncCrcValidation crc_validation = TsyncCrcValidation::kIgnored; + std::uint32_t global_time_sequence_counter_jump_width = 0; + TsyncSubTlvConfig sub_tlv_config{}; + bool is_valid = false; // set to true when the config values have been initialized +}; + +struct TsyncConsumerConfig { + std::string name{}; + TsyncTimeSlave time_slave_config{}; +}; + +struct TsyncTimeSyncCorrection { + std::chrono::milliseconds offset_correction_adaption_interval = std::chrono::milliseconds::zero(); + std::chrono::milliseconds offset_correction_jump_threshold = std::chrono::milliseconds::zero(); + std::chrono::milliseconds rate_deviation_measurement_duration = std::chrono::milliseconds::zero(); + std::uint32_t num_rate_corrections_per_measurement_duration = 0; + bool allow_provider_rate_correction = false; + bool is_valid = false; // set to true when the config values have been initialized +}; + +struct TsyncProviderConfig { + std::string name{}; + TsyncTimeMaster time_master_config{}; + TsyncTimeSyncCorrection time_sync_correction_config{}; +}; + +struct TsyncTimeDomainConfig { + TsyncEthTimeDomain eth_time_domain{}; + TsyncConsumerConfig consumer_config{}; + TsyncProviderConfig provider_config{}; + std::chrono::milliseconds sync_loss_timeout = std::chrono::milliseconds::zero(); + std::chrono::milliseconds debounce_time = std::chrono::milliseconds::zero(); + std::uint16_t domain_id = 0U; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TSYNCCONFIGTYPES_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/TsyncIdMappingsHandler.h b/src/tsync-utility-lib/include/score/time/utility/TsyncIdMappingsHandler.h new file mode 100644 index 0000000..b6ce9ed --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/TsyncIdMappingsHandler.h @@ -0,0 +1,116 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TSYNCIDMAPPINGSHANDLER_H_ +#define SCORE_TIME_UTILITY_TSYNCIDMAPPINGSHANDLER_H_ + +#include +#include +#include +#include +#include +#include + +#include "score/time/utility/ITimeBaseReader.h" +#include "score/time/utility/ITimeBaseWriter.h" +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TimeBaseWriterFactory.h" + +namespace score { +namespace time { + +/* Name of the shared memory file that holds the timebase identifier + * mappings */ +constexpr const char* kIdMappingsShmemFileName{"/tsync_id_mappings"}; +/* Size of the shared memory file that holds the timebase identifier + * mappings */ +constexpr size_t kIdMappingsShmemSize{8192u}; + +class TsyncIdMappingsHandler final { +public: + using TimeDomainId = uint32_t; + // domain id / domain name + using TimeDomainMapping = std::pair; + // consumer name / domain mapping + using TimeConsumerToTimeDomainMapping = std::pair; + // provider name / domain mapping + using TimeProviderToTimeDomainMapping = TimeConsumerToTimeDomainMapping; + using iterator = std::unordered_map::iterator; + using const_iterator = + std::unordered_map::const_iterator; + + constexpr TsyncIdMappingsHandler() = default; + TsyncIdMappingsHandler(const TsyncIdMappingsHandler&) = delete; + TsyncIdMappingsHandler(TsyncIdMappingsHandler&&); + TsyncIdMappingsHandler& operator=(const TsyncIdMappingsHandler&) = delete; + TsyncIdMappingsHandler& operator=(TsyncIdMappingsHandler&&); + ~TsyncIdMappingsHandler() = default; + + // timedomain mappings + bool AddDomainMapping(TimeDomainId domain_id, std::string_view domain_name) noexcept; + bool AddDomainMapping(const TimeDomainMapping& mapping) noexcept; + std::optional GetDomainId(std::string_view domain_name) noexcept; + std::optional GetDomainName(TimeDomainId domain_id) noexcept; + + // consumer to timedomain mappings + bool AddConsumerToDomain(std::string_view domain_name, std::string_view consumer_name) noexcept; + bool AddConsumerToDomain(TimeDomainId domain_id, std::string_view consumer_name) noexcept; + + std::optional GetDomainNameForConsumer(std::string_view consumer_name); + std::optional GetDomainIdForConsumer(std::string_view consumer_name); + + // provider to timedomain mappings (max 1 provider per domain) + bool AddProviderToDomain(std::string_view domain_name, std::string_view provider_name) noexcept; + bool AddProviderToDomain(TimeDomainId domain_id, std::string_view provider_name) noexcept; + std::optional GetProviderForDomain(std::string_view domain_name) noexcept; + std::optional GetProviderForDomain(TimeDomainId domain_id) noexcept; + std::optional GetDomainNameForProvider(std::string_view provider_name); + std::optional GetDomainIdForProvider(std::string_view provider_name); + + const_iterator cbegin() const noexcept; + iterator end() noexcept; + const_iterator cend() const noexcept; + iterator begin() noexcept; + + void Clear(); + + // persistency + void CommitMappingsToSharedMemory() noexcept; + void DoSharedMemoryRead() noexcept; + + // Debug + void DumpMappings() const; + + bool IsEmpty() const { + return time_domains_.empty(); + } + +private: + // coverity[autosar_cpp14_a0_1_1_violation:FALSE] #49860: This value is used subsequently. + static constexpr const TimeDomainId kMaxTimebaseId = 15u; + // coverity[autosar_cpp14_a0_1_1_violation:FALSE] #49860: This value is used subsequently. + static constexpr const std::size_t kMaxNumberOfTimeDomains = 16u; + // arbitrary upper limit of consumers per time domain to avoid using std::numeric_limits<>::max for boundary checks + // clang-format off + // coverity[autosar_cpp14_a0_1_1_violation:FALSE] #49860: This value is used subsequently. + static constexpr const std::size_t kMaxConsumersPerTimeDomain = 128u; + // clang-format on + + // TODO: use safe memory allocator (maybe) + std::unordered_map time_domains_{}; + std::unordered_map + consumer_to_time_domain_mappings_{}; + std::unordered_map + provider_to_time_domain_mappings_{}; + + TimeBaseWriterFactory::PointerType writer_{}; + TimeBaseReaderFactory::PointerType reader_{}; + bool AddConsumerToDomainMapping(uint32_t domain_id, std::string_view consumer_name) noexcept; + bool AddProviderToDomainMapping(uint32_t domain_id, std::string_view provider_name) noexcept; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TSYNCIDMAPPINGSHANDLER_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/TsyncNamedSemaphore.h b/src/tsync-utility-lib/include/score/time/utility/TsyncNamedSemaphore.h new file mode 100644 index 0000000..3fa96cc --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/TsyncNamedSemaphore.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TSYNCNAMEDSEMAPHORE_H_ +#define SCORE_TIME_UTILITY_TSYNCNAMEDSEMAPHORE_H_ + +#include + +#include + +namespace score { +namespace time { + +class TsyncNamedSemaphore final { +public: + enum class OpenMode { Signaled, Unsignaled }; + + TsyncNamedSemaphore(const std::string& name, OpenMode openMode, bool is_owner = false); + TsyncNamedSemaphore() = delete; + TsyncNamedSemaphore(const TsyncNamedSemaphore&) = delete; + TsyncNamedSemaphore& operator=(const TsyncNamedSemaphore&) = delete; + TsyncNamedSemaphore(TsyncNamedSemaphore&&); + TsyncNamedSemaphore& operator=(TsyncNamedSemaphore&&); + ~TsyncNamedSemaphore(); + + bool try_lock() noexcept; + int get_value() const noexcept; + // std::BasicLockable requirements + void lock() noexcept; + void unlock() noexcept; + +private: + void Destroy(); + void Move(TsyncNamedSemaphore&&); + + std::string name_{}; + // clang-format off + // RULECHECKER_comment(1, 1, check_c_style_cast, "Use of C-style cast forced by underlying POSIX API semantics.", true) + sem_t* sem_{SEM_FAILED}; + // clang-format on + bool is_owner_{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TSYNCNAMEDSEMAPHORE_H_ diff --git a/src/tsync-utility-lib/include/score/time/utility/TsyncSharedUtils.h b/src/tsync-utility-lib/include/score/time/utility/TsyncSharedUtils.h new file mode 100644 index 0000000..c1c1e4e --- /dev/null +++ b/src/tsync-utility-lib/include/score/time/utility/TsyncSharedUtils.h @@ -0,0 +1,133 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TSYNCSHAREDUTILS_H_ +#define SCORE_TIME_UTILITY_TSYNCSHAREDUTILS_H_ + +#include +#include +#include +#include +#include +#include + +#include "score/span.hpp" + +#include "score/time/synchronized_time_base_status.h" +#include "score/time/utility/TsyncNamedSemaphore.h" + +namespace score { +namespace time { + +using VirtualLocalTime = std::chrono::nanoseconds; +using UserData = std::array; +using UserDataView = score::cpp::span; + +struct TimestampWithStatus { + SynchronizationStatus status{}; + std::chrono::nanoseconds nanoseconds{}; + std::chrono::seconds seconds{}; +}; + +/// @brief Shared utilities class supply shared utilities functions for deamon, lib and ptp-lib +class TsyncSharedUtils final { +public: + /** + * @brief Create a new named semaphore object from an id + * + * @details + * The semaphore instance is owned by the caller. + * Using the domain id for the semaphore name. ptp-lib has no concept of domain names. + * Transmission is handled by calling TsyncNamedSemaphore::unlock() + * + * @param time_domain_id id of the time domain + * @param as_owner owner flag for named semaphore file, if true the semaphore will be created + * @return A TsyncNamedSamphore object + */ + static TsyncNamedSemaphore CreateTransmissionSemaphore(std::uint32_t time_domain_id, bool as_owner = false); + + /** + * @brief Get current virtual local time value + * + * @return std::optional std::nullopt: Failed + * a pointer current virtual local time in nanoseconds: Success + */ + static std::optional GetCurrentVirtualLocalTime(); + + /** + * @brief Get provider transmission semaphore name + * + * @param time_domain_id id of time domain + * @return the name of the semaphore file corresponding to time_domain_id + */ + static std::string GetTransmissionSemaphoreName(std::uint32_t time_domain_id); +}; + +// checks numeric limits and a given upper bound +template +constexpr inline + typename std::enable_if::value && std::is_integral::value, bool>::type + IsInBounds(OUT_TYPE a, ADDER_TYPE b, OUT_TYPE max) { + constexpr OUT_TYPE type_max = std::numeric_limits::max(); + // make sure a+b does not wrap + return ((max < type_max) && (max > b) && (max - b > a)); +} + +// only checks numeric limits +template +constexpr inline + typename std::enable_if::value && std::is_integral::value, bool>::type + IsInBounds(OUT_TYPE a, ADDER_TYPE b) { + constexpr OUT_TYPE type_max = std::numeric_limits::max(); + // make sure a+b does not wrap + return (type_max - b > a); +} + +template +constexpr inline + typename std::enable_if::value && std::is_integral::value, bool>::type + SafeAdd(RESULT_TYPE& a, ADDER_TYPE b, RESULT_TYPE max) { + if (IsInBounds(a, b, max)) { + a += static_cast(b); + return true; + } + + return false; +} + +// only checks numeric limits +template +constexpr inline + typename std::enable_if::value && std::is_integral::value, bool>::type + SafeAdd(RESULT_TYPE& a, ADDER_TYPE b) { + if (IsInBounds(a, b)) { + a += static_cast(b); + return true; + } + + return false; +} + +template +constexpr inline typename std::enable_if::value && std::is_integral::value && + std::is_integral::value, + bool>::type +SafeSub(RESULT_TYPE& a, SUBTRACTOR_TYPE b) { + if (b > a) { + return false; + } + + a -= b; + return true; +} + +template +constexpr auto make_array(Args&&... args) { + return std::array{static_cast(std::forward(args))...}; +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TSYNCSHAREDUTILS_H_ diff --git a/src/tsync-utility-lib/src/SharedMemLayout.h b/src/tsync-utility-lib/src/SharedMemLayout.h new file mode 100644 index 0000000..2f54705 --- /dev/null +++ b/src/tsync-utility-lib/src/SharedMemLayout.h @@ -0,0 +1,24 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_SHAREDMEMLAYOUT_H_ +#define SCORE_TIME_UTILITY_SHAREDMEMLAYOUT_H_ + +#include + +namespace score { +namespace time { + +constexpr std::size_t kSharedMemPageSize = 4096u; +constexpr std::size_t kSharedMemTimebaseDataOffset = 0u; +constexpr std::size_t kSharedMemTimebaseConfigOffset = 2048u; + +constexpr std::size_t kSharedMemTimebaseDomainConfigOffset = kSharedMemTimebaseConfigOffset; +constexpr std::size_t kSharedMemTimebaseConsumerConfigOffset = kSharedMemTimebaseDomainConfigOffset + 512u; +constexpr std::size_t kSharedMemTimebaseProviderConfigOffset = kSharedMemTimebaseConsumerConfigOffset + 512u; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_SHAREDMEMLAYOUT_H_ diff --git a/src/tsync-utility-lib/src/SharedMemTimeBaseReader.cpp b/src/tsync-utility-lib/src/SharedMemTimeBaseReader.cpp new file mode 100644 index 0000000..af39065 --- /dev/null +++ b/src/tsync-utility-lib/src/SharedMemTimeBaseReader.cpp @@ -0,0 +1,419 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SharedMemTimeBaseReader.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/SysCalls.h" +#include "score/time/utility/TsyncConfigTypes.h" +#include "score/time/utility/TsyncSharedUtils.h" +#include "SharedMemLayout.h" + +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { + +namespace helpers { +template +std::size_t ReadField(SharedMemTimeBaseReader& reader, FIELDTYPE& data) /* noexcept */; + +// template specializations allowing to optimize deserialization +// for types without special alignment requirements. +// *Note*: These can only be done at the namespace level, not at the class level. +template <> +std::size_t ReadField(SharedMemTimeBaseReader& reader, std::string& data) /* noexcept */ { + if (reader.GetState() == ITimeBaseAccessor::State::Closed) { + return 0u; + } + + std::size_t pos_start = reader.GetPosition(); + + // read the string size + std::uint64_t sz; + bool res = reader.Read(sz); + + if (!res) { + return 0; + } + + if (sz == 0u) { + data.clear(); + return sizeof(sz); + } + + // add one for terminating zero + if (!SafeAdd(sz, 1u, reader.GetMaxSize())) { + static_cast(reader.SetPosition(pos_start)); + return 0u; + } + + // zero copy read of the string data + const char* p = nullptr; + if (!reader.Read(&p, sz)) { + static_cast(reader.SetPosition(pos_start)); + return 0u; + } + + // assign the raw string data & verify the string length + data = p; + if (data.length() != sz - 1) { + static_cast(reader.SetPosition(pos_start)); + return 0u; + } + + std::size_t bytes_read = reader.GetPosition(); + static_cast(SafeSub(bytes_read, pos_start)); + return bytes_read; +} + +template <> +std::size_t ReadField(SharedMemTimeBaseReader& reader, std::string_view& data) /* noexcept */ { + if (reader.GetState() == ITimeBaseAccessor::State::Closed) { + return 0u; + } + + std::size_t pos_start = reader.GetPosition(); + + // read the stringview size + std::uint64_t sz; + if (!reader.Read(sz)) { + return 0u; + } + + if (sz == 0u) { + data = std::string_view(); + return sizeof(sz); + } + + std::size_t end_pos = reader.GetPosition(); + if (!SafeAdd(end_pos, sz, reader.GetMaxSize())) { + static_cast(reader.SetPosition(pos_start)); + return 0u; + } + + // zero copy read of the string data + const char* p = nullptr; + (void)reader.Read(&p, sz); + + // assign the raw string data + data = std::string_view(p, sz); + + std::size_t bytes_read = reader.GetPosition(); + static_cast(SafeSub(bytes_read, pos_start)); + return bytes_read; +} + +} // namespace helpers + +// clang-format off +SharedMemTimeBaseReader::SharedMemTimeBaseReader(std::string_view name) noexcept + : SharedMemTimeBaseReader(name, kSharedMemPageSize) { +} + +// coverity[exn_spec_violation] Size of std::string_view will never exceed std::string.max_size(), so no length error will be thrown +SharedMemTimeBaseReader::SharedMemTimeBaseReader(std::string_view name, std::size_t max_size) noexcept + : ITimeBaseAccessor{}, ITimeBaseReader{}, time_base_name_(name), max_size_(max_size) { + shm_name_ = time_base_name_.front() != '/' ? std::string("/") + time_base_name_ : time_base_name_; +} +// clang-format on + +SharedMemTimeBaseReader::~SharedMemTimeBaseReader() noexcept { + Close(); +} + +void SharedMemTimeBaseReader::Open() /* noexcept */ { + if (state_ == State::Open) { + // allow Open() to be called repeatedly with (almost) no runtime penalty + return; + } + + auto fileDescriptor = + OsShmOpen(shm_name_.c_str(), O_RDWR, static_cast(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + if (fileDescriptor < 0) { + std::stringstream ss; + ss << "SharedMemTimeBaseReader::Open() - " + << "Unable to open shared memory file" + << " for " << shm_name_.c_str() << " with error " << errno; + logFatalAndAbort(ss.str().c_str()); + } + + // Open with write access to be able to create the TsyncReadWriteLock instance in + // shared memory + void* v_addr = OsMmap(nullptr, max_size_, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); + // clang-format off + if (v_addr == MAP_FAILED) { + // clang-format on + OsClose(fileDescriptor); + std::stringstream ss; + ss << "SharedMemTimeBaseReader::Open() - OsMmap failed" + << " for " << shm_name_.c_str() << " with error " << errno; + logFatalAndAbort(ss.str().c_str()); + } + addr_ = static_cast(v_addr); + + rw_lock_.Open(static_cast(v_addr), TsyncReadWriteLock::LockMode::Read, false); + // Now map shared mem again with readonly access + v_addr = OsMmap(nullptr, max_size_, PROT_READ, MAP_SHARED, fileDescriptor, 0); + OsClose(fileDescriptor); + // clang-format off + if (v_addr == MAP_FAILED) { + // clang-format on + std::stringstream ss; + ss << "SharedMemTimeBaseReader::Open() - OsMmap failed" + << " for " << shm_name_.c_str() << " with error " << errno; + logFatalAndAbort(ss.str().c_str()); + } + addr_read_only_ = static_cast(v_addr); + + // not testable, currently the alignment matches + // if (current_read_offset_ % alignof(std::max_align_t) != 0) { + // AlignedSkip(*this); + // } + + state_ = State::Open; + + if (!(SetPosition(sizeof(TsyncReadWriteLock) + kSharedMemTimebaseDataOffset))) { + logFatalAndAbort("SharedMemTimeBaseReader::Open() failed. MaxSize is too small."); + } +} + +ITimeBaseAccessor& SharedMemTimeBaseReader::GetAccessor() /* noexcept */ { + return *this; +} + +bool SharedMemTimeBaseReader::Read(std::uint8_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::uint16_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::uint32_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::uint64_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::int8_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::int16_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::int32_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(std::int64_t& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(float& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(double& data) /* noexcept */ { + return (ReadField(data) > 0U); +} + +bool SharedMemTimeBaseReader::Read(TimestampWithStatus& data) /* noexcept */ { + return (ReadField(data) > 0u); +} + +bool SharedMemTimeBaseReader::Read(VirtualLocalTime& data) /* noexcept */ { + return (ReadField(data) > 0u); +} + +bool SharedMemTimeBaseReader::Read(UserDataView& data) /* noexcept */ { + // We're always reading 4 bytes of UserData + constexpr std::size_t kUserMaxDataSize{4u}; + const char* p; + bool res{Read(&p, kUserMaxDataSize)}; + + if (res) { + // clang-format off + // RULECHECKER_comment(1, 1, check_reinterpret_cast, "This cast is well defined according to the C++ type aliasing rules.", true) + UserDataView::pointer pb = reinterpret_cast(p); + // clang-format on + // the actual user data length is stored in the first element. + std::size_t sz = static_cast(pb[0]); + if (sz == 0u) { + data = UserDataView{}; + } else if (sz > kUserMaxDataSize - 1u) { + res = false; + } else { + data = UserDataView(&pb[1], sz); + } + } + return res; +} + +bool SharedMemTimeBaseReader::Read(SynchronizationStatus& data) /* noexcept */ { + return (ReadField(data) > 0u); +} + +// Note: this only works for types that have an alignment of 1 since we've switched to aligned reads/writes +bool SharedMemTimeBaseReader::Read(const char** p, std::size_t num_bytes) /* noexcept */ { + if (state_ == State::Open && IsInBounds(current_read_offset_, num_bytes, max_size_) && + p != nullptr) { + *p = addr_read_only_ + current_read_offset_; + return SetPosition(current_read_offset_ + num_bytes); + } + + return false; +} + +bool SharedMemTimeBaseReader::Read(TsyncTimeDomainConfig& data) /* noexcept */ { + bool res = false; + + if (state_ == State::Open) { + std::size_t pos = current_read_offset_; + res = (SetPosition(kSharedMemTimebaseDomainConfigOffset) && (ReadField(data.eth_time_domain) != 0U) && + Read(data.consumer_config) && Read(data.provider_config) && (ReadField(data.sync_loss_timeout) != 0U) && + (ReadField(data.debounce_time) != 0U) && Read(data.domain_id)); + static_cast(SetPosition(pos)); + } + + return res; +} + +bool SharedMemTimeBaseReader::Read(TsyncConsumerConfig& data) /* noexcept */ { + bool res = false; + + if (state_ == State::Open) { + std::size_t pos = current_read_offset_; + res = (SetPosition(kSharedMemTimebaseConsumerConfigOffset) && Read(data.name) && + ReadField(data.time_slave_config) != 0u); + + static_cast(SetPosition(pos)); + } + + return res; +} + +bool SharedMemTimeBaseReader::Read(TsyncProviderConfig& data) /* noexcept */ { + bool res = false; + + if (state_ == State::Open) { + std::size_t pos = current_read_offset_; + + res = (SetPosition(kSharedMemTimebaseProviderConfigOffset) && Read(data.name) && + (ReadField(data.time_master_config) != 0U) && (ReadField(data.time_sync_correction_config) != 0U)); + + static_cast(SetPosition(pos)); + } + + return res; +} + +bool SharedMemTimeBaseReader::Read(std::string& data) /* noexcept */ { + return (helpers::ReadField(*this, data) > 0); +} + +bool SharedMemTimeBaseReader::Read(std::string_view& data) /* noexcept */ { + return (helpers::ReadField(*this, data) != 0); +} + +bool SharedMemTimeBaseReader::Skip(std::size_t num_bytes) /* noexcept */ { + if (state_ == State::Open) { + auto pos{current_read_offset_.load()}; + if (SafeAdd(pos, num_bytes, max_size_)) { + current_read_offset_ = pos; + return true; + } + } + + return false; +} + +bool SharedMemTimeBaseReader::SetPosition(std::size_t pos) /* noexcept */ { + if (state_ == State::Open && pos < max_size_ && pos >= sizeof(TsyncReadWriteLock)) { + current_read_offset_ = pos; + return true; + } + + return false; +} + +std::size_t SharedMemTimeBaseReader::GetPosition() const /* noexcept */ { + return current_read_offset_; +} + +std::size_t SharedMemTimeBaseReader::GetMaxSize() const /* noexcept */ { + return max_size_; +} + +void SharedMemTimeBaseReader::lock() /* noexcept */ { + if (state_ == State::Open) { + rw_lock_.lock(); + if (!(SetPosition(sizeof(TsyncReadWriteLock) + kSharedMemTimebaseDataOffset))) { + logFatalAndAbort("SharedMemTimeBaseReader::lock(): MaxSize is too small"); + } + } else { + logFatalAndAbort("lock called on closed reader"); + } +} + +void SharedMemTimeBaseReader::unlock() /* noexcept */ +{ + if (state_ == State::Open) { + rw_lock_.unlock(); + } else { + logFatalAndAbort("unlock called on closed reader"); + } +} + +void SharedMemTimeBaseReader::Close() /* noexcept */ { + if (state_ == ITimeBaseAccessor::State::Open) { + state_ = ITimeBaseAccessor::State::Closed; + + if (addr_) { + if (OsMunmap(addr_, max_size_) < 0) { + std::cerr << "SharedMemTimeBaseReader::Close - munmap failed with " << errno << "\n"; + } else { + addr_ = nullptr; + } + } + + if (addr_read_only_) { + if (OsMunmap(addr_read_only_, max_size_) < 0) { + std::cerr << "SharedMemTimeBaseReader::Close - munmap failed with " << errno << "\n"; + } else { + addr_read_only_ = nullptr; + } + } + } +} + +std::string_view SharedMemTimeBaseReader::GetName() const /* noexcept */ { + return time_base_name_; +} + +ITimeBaseAccessor::State SharedMemTimeBaseReader::GetState() const /* noexcept */ { + return state_; +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/SharedMemTimeBaseReader.h b/src/tsync-utility-lib/src/SharedMemTimeBaseReader.h new file mode 100644 index 0000000..2c8d0b9 --- /dev/null +++ b/src/tsync-utility-lib/src/SharedMemTimeBaseReader.h @@ -0,0 +1,103 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_SHAREDMEMTIMEBASEREADER_H_ +#define SCORE_TIME_UTILITY_SHAREDMEMTIMEBASEREADER_H_ + +#include +#include + +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/ITimeBaseReader.h" +#include "TsyncReadWriteLock.h" + +namespace score { +namespace time { + +// clang-format off +// RULECHECKER_comment(1, 1, check_multiple_non_interface_bases, "wi 48449 - Inheriting a class with special member functions is not considered an Interface class.", false) +class SharedMemTimeBaseReader final : public ITimeBaseAccessor, public ITimeBaseReader { + // clang-format on +public: + SharedMemTimeBaseReader(std::string_view name) noexcept; + SharedMemTimeBaseReader(std::string_view name, std::size_t max_size) noexcept; + ~SharedMemTimeBaseReader() noexcept; + SharedMemTimeBaseReader() = delete; + SharedMemTimeBaseReader(const SharedMemTimeBaseReader&) = delete; + SharedMemTimeBaseReader& operator=(const SharedMemTimeBaseReader&) = delete; + + // ITimeBaseReader + ITimeBaseAccessor& GetAccessor() /* noexcept */ override; + + bool Read(std::uint8_t& data) /* noexcept */ override; + bool Read(std::uint16_t& data) /* noexcept */ override; + bool Read(std::uint32_t& data) /* noexcept */ override; + bool Read(std::uint64_t& data) /* noexcept */ override; + bool Read(std::int8_t& data) /* noexcept */ override; + bool Read(std::int16_t& data) /* noexcept */ override; + bool Read(std::int32_t& data) /* noexcept */ override; + bool Read(std::int64_t& data) /* noexcept */ override; + bool Read(float& data) /* noexcept */ override; + bool Read(double& data) /* noexcept */ override; + bool Read(std::string& data) /* noexcept */ override; + bool Read(std::string_view& data) /* noexcept */ override; + bool Read(const char** p, std::size_t num_bytes) /* noexcept */ override; + + // Function to remove dependency from ptp-lib + bool Read(TimestampWithStatus& data) /* noexcept */ override; + bool Read(VirtualLocalTime& data) /* noexcept */ override; + bool Read(UserDataView& data) /* noexcept */ override; + bool Read(SynchronizationStatus& data) /* noexcept */ override; + + bool Read(TsyncTimeDomainConfig& data) /* noexcept */ override; + bool Read(TsyncConsumerConfig& data) /* noexcept */ override; + bool Read(TsyncProviderConfig& data) /* noexcept */ override; + + bool Skip(std::size_t num_bytes) /* noexcept */ override; + bool SetPosition(std::size_t pos) /* noexcept */ override; + std::size_t GetPosition() const /* noexcept */ override; + std::size_t GetMaxSize() const /* noexcept */ override; + + void lock() /* noexcept */ override; + void unlock() /* noexcept */ override; + + // ITimeBaseAccessor + void Open() /* noexcept */ override; + void Close() /* noexcept */ override; + std::string_view GetName() const /* noexcept */ override; + State GetState() const /* noexcept */ override; + +private: + const std::string time_base_name_{}; + std::string shm_name_{}; + TsyncReadWriteLock rw_lock_{}; + const std::size_t max_size_{}; + char* addr_ = nullptr; + char* addr_read_only_ = nullptr; + std::atomic current_read_offset_{0}; + std::atomic state_{State::Closed}; + + template + std::size_t ReadField(FIELDTYPE& data) /* noexcept */ { + if (GetState() == ITimeBaseAccessor::State::Closed) { + return 0u; + } + bool res = Align(*this); + + if (!res) { + return 0u; + } + + std::size_t pos = GetPosition(); + + std::memcpy(&data, addr_read_only_ + pos, sizeof(FIELDTYPE)); + static_cast(SetPosition(pos + sizeof(FIELDTYPE))); + return sizeof(FIELDTYPE); + } +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_SHAREDMEMTIMEBASEREADER_H_ diff --git a/src/tsync-utility-lib/src/SharedMemTimeBaseWriter.cpp b/src/tsync-utility-lib/src/SharedMemTimeBaseWriter.cpp new file mode 100644 index 0000000..cdc4f66 --- /dev/null +++ b/src/tsync-utility-lib/src/SharedMemTimeBaseWriter.cpp @@ -0,0 +1,418 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SharedMemTimeBaseWriter.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/SysCalls.h" +#include "score/time/utility/TsyncConfigTypes.h" +#include "score/time/utility/TsyncSharedUtils.h" +#include "SharedMemLayout.h" + +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { + +namespace helpers { +template +std::size_t WriteField(SharedMemTimeBaseWriter& writer, const FIELDTYPE& data); + +// template specializations allowing to optimize serialization +// for types without special alignment requirements. +// *Note*: These can only be done at the namespace level, not at the class level. + +template <> +std::size_t WriteField(SharedMemTimeBaseWriter& writer, const std::string& data) { + if (writer.GetState() == ITimeBaseAccessor::State::Open) { + const std::size_t start_pos = writer.GetPosition(); + const std::uint64_t str_len = data.size(); + bool res = writer.Write(str_len); + if (res) { + if (str_len == 0u) { + return sizeof(str_len); + } + + // add 1 for terminating zero + uint64_t wsz = str_len; + static_cast(SafeAdd(wsz, 1u)); + if (writer.Write(data.c_str(), wsz)) { + std::size_t sz_written = sizeof(str_len); + static_cast(SafeAdd(sz_written, str_len)); + static_cast(SafeAdd(sz_written, 1u)); + + return sz_written; + } + } + static_cast(writer.SetPosition(start_pos)); + } + + return 0u; +} + +template <> +std::size_t WriteField(SharedMemTimeBaseWriter& writer, const std::string_view& data) { + if (writer.GetState() == ITimeBaseAccessor::State::Open) { + std::size_t pos_start = writer.GetPosition(); + std::size_t sz = data.length(); + bool res = writer.Write(static_cast(sz)); + if (res) { + if (sz == 0u) { + return sizeof(sz); + } + + res = writer.Write(data.data(), sz); + if (res) { + std::size_t sz_written = sz; + // add the size of the length info + static_cast(SafeAdd(sz_written, sizeof(sz), writer.GetMaxSize())); + return sz_written; + } + + static_cast(writer.SetPosition(pos_start)); + } + } + + return 0u; +} + +} // namespace helpers + +// clang-format off +SharedMemTimeBaseWriter::SharedMemTimeBaseWriter(std::string_view name, bool is_owner) /* noexcept */ + : SharedMemTimeBaseWriter(name, kSharedMemPageSize, is_owner) { +} + +// coverity[exn_spec_violation] Size of std::string_view will never exceed std::string.max_size(), so no length error will be thrown +SharedMemTimeBaseWriter::SharedMemTimeBaseWriter(std::string_view name, std::size_t max_size, bool is_owner) /* noexcept */ + : ITimeBaseAccessor{}, ITimeBaseWriter{}, time_base_name_(name), max_size_(max_size), is_owner_(is_owner) { + shm_name_ = time_base_name_.front() != '/' ? std::string("/") + time_base_name_ : time_base_name_; +} +// clang-format on + +// coverity[exn_spec_violation] The string created in Unlink() will never throw a length error +SharedMemTimeBaseWriter::~SharedMemTimeBaseWriter() /* noexcept */ { + Close(); + Unlink(); +} + +void SharedMemTimeBaseWriter::Open() /* noexcept */ { + if (state_ == State::Open) { + // allow Open() to be called repeatedly with (almost) no runtime penalty + return; + } + + auto fileDescriptor = OsShmOpen(shm_name_.c_str(), O_RDWR | (is_owner_ ? O_CREAT : 0), + static_cast(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + if (fileDescriptor < 0) { + std::stringstream ss; + ss << "SharedMemTimeBaseWriter::Open() - " + << "Unable to open shared memory file" + << " for " << shm_name_.c_str() << " with error " << errno; + logFatalAndAbort(ss.str().c_str()); + } + + if (is_owner_) { + if (OsFtruncate(fileDescriptor, static_cast(max_size_)) != 0) { + std::stringstream ss; + ss << "SharedMemTimeBaseWriter::Open() - " + << "Unable to set shared memory block size" + << " for " << shm_name_.c_str() << " with error " << errno; + logFatalAndAbort(ss.str().c_str()); + } + } + + void* v_addr = OsMmap(nullptr, max_size_, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); + OsClose(fileDescriptor); + if (v_addr == MAP_FAILED) { + std::stringstream ss; + ss << "SharedMemTimeBaseWriter::Open() - OsMmap failed" + << " for " << shm_name_.c_str() << " with error " << errno; + logFatalAndAbort(ss.str().c_str()); + } + + addr_ = static_cast(v_addr); + rw_lock_.Open(static_cast(v_addr), TsyncReadWriteLock::LockMode::Write, is_owner_); + + // not testable, currently the alignment matches + // if (current_write_offset_ % alignof(std::max_align_t) != 0) { + // AlignedSkip(*this); + // } + + state_ = State::Open; + + if (!(SetPosition(sizeof(TsyncReadWriteLock) + kSharedMemTimebaseDataOffset))) { + logFatalAndAbort("SharedMemTimeBaseWriter::Open() failed. MaxSize is too small."); + } +} + +void SharedMemTimeBaseWriter::Close() /* noexcept */ { + if (state_ == ITimeBaseAccessor::State::Open) { + state_ = ITimeBaseAccessor::State::Closed; + rw_lock_.Close(); + if (addr_) { + if (OsMunmap(addr_, max_size_) < 0) { + std::cerr << "SharedMemTimeBase::Close - munmap failed with error " << errno << "\n"; + } else { + addr_ = nullptr; + } + } + } +} + +std::string_view SharedMemTimeBaseWriter::GetName() const /* noexcept */ { + return time_base_name_; +} + +ITimeBaseAccessor::State SharedMemTimeBaseWriter::GetState() const /* noexcept */ { + return state_; +} + +ITimeBaseAccessor& SharedMemTimeBaseWriter::GetAccessor() /* noexcept */ { + return *this; +} + +bool SharedMemTimeBaseWriter::Write(std::uint8_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::uint16_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::uint32_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::uint64_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::int8_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::int16_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::int32_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(std::int64_t data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(float data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(double data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(const TimestampWithStatus& data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(const VirtualLocalTime& data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::Write(UserData data) /* noexcept */ { + // We're always writing 4 bytes of UserData + void* pv = data.data(); + return Write(static_cast(pv), 4u); +} + +bool SharedMemTimeBaseWriter::Write(UserDataView data) /* noexcept */ { + if (GetState() == ITimeBaseAccessor::State::Closed) { + return false; + } + + if (data.size() == 0u) { + return Write(make_array(0, 0, 0, 0)); + } + + // We're always writing 4 bytes of UserData (UD length + 3 bytes) + constexpr std::size_t kUserDataMax{3u}; + + std::size_t sz = std::min(data.size(), kUserDataMax); + // clang-format off + // RULECHECKER_comment(1, 1, check_reinterpret_cast_extended, "This cast is well defined according to the C++ type aliasing rules.", true) + const char* p = reinterpret_cast(data.data()); + // clang-format on + if (Write(static_cast(sz)) && Write(p, sz)) { + return Skip(kUserDataMax - sz); + } + + return false; +} + +bool SharedMemTimeBaseWriter::Write(const SynchronizationStatus& data) /* noexcept */ { + return (WriteField(data) > 0u); +} + +bool SharedMemTimeBaseWriter::WriteDefaults() /* noexcept */ { + if (state_ == State::Closed) { + return false; + } + + TimestampWithStatus ts{SynchronizationStatus::kNotSynchronizedUntilStartup, std::chrono::nanoseconds(0), + std::chrono::seconds(0)}; + VirtualLocalTime vt(0); + UserData ud = {std::byte{0}, std::byte{0}, std::byte{0}, std::byte{0}}; + + return (Write(ts) && Write(vt) && Write(ud)); +} + +bool SharedMemTimeBaseWriter::Write(const TsyncTimeDomainConfig& data) /* noexcept */ { + bool res = false; + + if (state_ == State::Open) { + std::size_t pos = current_write_offset_; + SetPosition(kSharedMemTimebaseDomainConfigOffset); + res = (SetPosition(kSharedMemTimebaseDomainConfigOffset) && (WriteField(data.eth_time_domain) != 0u) && + Write(data.consumer_config) && Write(data.provider_config) && (WriteField(data.sync_loss_timeout) > 0) && + (WriteField(data.debounce_time) > 0) && Write(data.domain_id)); + static_cast(SetPosition(pos)); + } + + return res; +} + +bool SharedMemTimeBaseWriter::Write(const TsyncConsumerConfig& data) /* noexcept */ { + bool res = false; + + if (state_ == State::Open) { + std::size_t pos = current_write_offset_; + res = (SetPosition(kSharedMemTimebaseConsumerConfigOffset) && Write(data.name) && + (WriteField(data.time_slave_config) > 0u)); + static_cast(SetPosition(pos)); + } + + return res; +} + +bool SharedMemTimeBaseWriter::Write(const TsyncProviderConfig& data) /* noexcept */ { + bool res = false; + + if (state_ == State::Open) { + std::size_t pos = current_write_offset_; + res = (SetPosition(kSharedMemTimebaseProviderConfigOffset) && Write(data.name) && + (WriteField(data.time_master_config) != 0u) && (WriteField(data.time_sync_correction_config) != 0u)); + static_cast(SetPosition(pos)); + } + + return res; +} + +bool SharedMemTimeBaseWriter::Write(const std::string& data) { + return (helpers::WriteField(*this, data) > 0); +} + +bool SharedMemTimeBaseWriter::Write(std::string_view data) { + return (helpers::WriteField(*this, data) > 0); +} + +bool SharedMemTimeBaseWriter::Write(score::cpp::span data) /* noexcept */ { + if (state_ == State::Open) { + std::size_t pos{current_write_offset_.load()}; + if (IsInBounds(pos, data.size(), max_size_)) { + std::memcpy(addr_ + current_write_offset_, data.data(), data.size()); + return Skip(data.size()); + } + } + return false; +} + +bool SharedMemTimeBaseWriter::Write(const char* data, std::size_t size) { + if (state_ == State::Open) { + if (size == 0u) { + return true; + } + + std::size_t pos{current_write_offset_.load()}; + if (IsInBounds(pos, size, max_size_)) { + std::memcpy(addr_ + current_write_offset_, data, size); + return Skip(size); + } + } + return false; +} + +bool SharedMemTimeBaseWriter::Skip(std::size_t num_bytes) /* noexcept */ { + if (state_ == State::Open) { + auto pos{current_write_offset_.load()}; + if (SafeAdd(pos, num_bytes, max_size_)) { + current_write_offset_ = pos; + return true; + } + } + + return false; +} + +bool SharedMemTimeBaseWriter::SetPosition(std::size_t pos) /* noexcept */ { + if (state_ == State::Open && pos < max_size_ && pos >= sizeof(TsyncReadWriteLock)) { + current_write_offset_ = pos; + return true; + } + + return false; +} + +std::size_t SharedMemTimeBaseWriter::GetPosition() const /* noexcept */ { + return current_write_offset_; +} + +std::size_t SharedMemTimeBaseWriter::GetMaxSize() const /* noexcept */ { + return max_size_; +} + +void SharedMemTimeBaseWriter::lock() /* noexcept */ { + if (state_ == State::Open) { + rw_lock_.lock(); + if (!(SetPosition(sizeof(TsyncReadWriteLock) + kSharedMemTimebaseDataOffset))) { + logFatalAndAbort("SharedMemTimeBaseWriter::lock(): MaxSize is too small"); + } + } else { + logFatalAndAbort("lock called on closed writer"); + } +} +void SharedMemTimeBaseWriter::unlock() /* noexcept */ { + if (state_ == State::Open) { + rw_lock_.unlock(); + } else { + logFatalAndAbort("unlock called on closed writer"); + } +} + +void SharedMemTimeBaseWriter::Unlink() { + if (is_owner_) { + auto s_len = time_base_name_.size(); + + if (s_len > 0U && time_base_name_.back() == '/') { + s_len--; + } + + if (OsShmUnlink(time_base_name_.substr(0u, s_len).c_str()) != 0) { + std::perror("SharedMemTimeBaseWriter::Unlink(): unlinking failed!"); + } + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/SharedMemTimeBaseWriter.h b/src/tsync-utility-lib/src/SharedMemTimeBaseWriter.h new file mode 100644 index 0000000..7416f83 --- /dev/null +++ b/src/tsync-utility-lib/src/SharedMemTimeBaseWriter.h @@ -0,0 +1,114 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_SHAREDMEMTIMEBASEWRITER_H_ +#define SCORE_TIME_UTILITY_SHAREDMEMTIMEBASEWRITER_H_ + +#include +#include +#include +#include +#include + +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/ITimeBaseWriter.h" +#include "TsyncReadWriteLock.h" + +namespace score { +namespace time { + +// clang-format off +// RULECHECKER_comment(1, 1, check_multiple_non_interface_bases, "wi 48449 - Inheriting a class with special member functions is not considered an Interface class.", false) +class SharedMemTimeBaseWriter final : public ITimeBaseAccessor, public ITimeBaseWriter { + // clang-format on +public: + SharedMemTimeBaseWriter(std::string_view name, bool is_owner = false) /* noexcept*/; + SharedMemTimeBaseWriter(std::string_view name, std::size_t max_size, bool is_owner = false) /* noexcept*/; + ~SharedMemTimeBaseWriter() /* noexcept */; + SharedMemTimeBaseWriter() = delete; + SharedMemTimeBaseWriter(const SharedMemTimeBaseWriter&) = delete; + SharedMemTimeBaseWriter& operator=(const SharedMemTimeBaseWriter&) = delete; + + // ITimeBaseWriter + ITimeBaseAccessor& GetAccessor() /* noexcept */ override; + + bool Write(std::uint8_t data) /* noexcept */ override; + bool Write(std::uint16_t data) /* noexcept */ override; + bool Write(std::uint32_t data) /* noexcept */ override; + bool Write(std::uint64_t data) /* noexcept */ override; + bool Write(std::int8_t data) /* noexcept */ override; + bool Write(std::int16_t data) /* noexcept */ override; + bool Write(std::int32_t data) /* noexcept */ override; + bool Write(std::int64_t data) /* noexcept */ override; + bool Write(float data) /* noexcept */ override; + bool Write(double data) /* noexcept */ override; + bool Write(const std::string& data) /* noexcept */ override; + bool Write(std::string_view data) /* noexcept */ override; + bool Write(score::cpp::span data) /* noexcept */ override; + // Function to remove dependency on ptp-lib********************************** + bool Write(const TimestampWithStatus& data) /* noexcept */ override; + bool Write(const VirtualLocalTime& data) /* noexcept */ override; + bool Write(UserData data) /* noexcept */ override; + bool Write(UserDataView data) /* noexcept */ override; + bool Write(const SynchronizationStatus& data) /* noexcept */ override; + + bool WriteDefaults() /* noexcept */ override; + + bool Write(const TsyncTimeDomainConfig& data) /* noexcept */ override; + bool Write(const TsyncConsumerConfig& data) /* noexcept */ override; + bool Write(const TsyncProviderConfig& data) /* noexcept */ override; + + bool Write(const char* data, std::size_t size) override; + + bool Skip(std::size_t num_bytes) /* noexcept */ override; + bool SetPosition(std::size_t pos) /* noexcept */ override; + std::size_t GetPosition() const /* noexcept */ override; + std::size_t GetMaxSize() const /* noexcept */ override; + + void lock() /* noexcept */ override; + void unlock() /* noexcept */ override; + + // ITimeBaseAccessor + void Open() /* noexcept */ override; + void Close() /* noexcept */ override; + std::string_view GetName() const /* noexcept */ override; + State GetState() const /* noexcept */ override; + +private: + template + std::size_t WriteField(const FIELDTYPE& data) /* noexcept */ { + if (GetState() == ITimeBaseAccessor::State::Closed) { + return 0u; + } + + bool res = Align(*this); + + if (!res) { + return 0u; + } + + std::size_t pos = GetPosition(); + + std::memcpy(addr_ + pos, &data, sizeof(FIELDTYPE)); + static_cast(SetPosition(pos + sizeof(FIELDTYPE))); + return sizeof(FIELDTYPE); + } + + void Unlink(); + +private: + const std::string time_base_name_{}; + std::string shm_name_{}; + TsyncReadWriteLock rw_lock_{}; + const std::size_t max_size_{}; + char* addr_ = nullptr; + std::atomic current_write_offset_{0}; + std::atomic state_{State::Closed}; + bool is_owner_{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_SHAREDMEMTIMEBASEWRITER_H_ diff --git a/src/tsync-utility-lib/src/SysCalls.cpp b/src/tsync-utility-lib/src/SysCalls.cpp new file mode 100644 index 0000000..d84cdf1 --- /dev/null +++ b/src/tsync-utility-lib/src/SysCalls.cpp @@ -0,0 +1,144 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/utility/SysCalls.h" + +#include /* For O_* constants */ +#include + +#include + +#include "score/time/common/ExcludeCoverageAdapter.h" + +namespace score { +namespace time { + +EXCLUDE_COVERAGE_START( + "System call wrapper functions are used for mocking in unit tests, it does not make sense to write unit tests for them.") + +int OsShmOpen(const char* name, int oflag, mode_t mode) { + return shm_open(name, oflag, mode); +} + +int OsShmUnlink(const char* name) { + return shm_unlink(name); +} + +int OsFtruncate(int fd, off_t length) { + return ftruncate(fd, length); +} + +// clang-format off +// RULECHECKER_comment(1, 1, check_max_parameters, "The number of arguments reflects the signature of the underlying POSIX API.", true) +void* OsMmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset) { + // clang-format on + return mmap(addr, len, prot, flags, fd, offset); +} + +int OsMunmap(void* addr, size_t len) { + return munmap(addr, len); +} + +int OsClose(int fd) { + return close(fd); +} + +sem_t* OsSemOpen(const char* name, int oflag) { + return sem_open(name, oflag); +} + +// clang-format off +// RULECHECKER_comment(1, 1, check_max_parameters, "The number of arguments reflects the signature of the underlying POSIX API.", true) +sem_t* OsSemOpen(const char* name, int oflag, mode_t mode, unsigned int value) { + // clang-format on + return sem_open(name, oflag, mode, value); +} + +int OsSemClose(sem_t* sem) { + return sem_close(sem); +} + +mode_t OsUmask(mode_t mode) { + return umask(mode); +} + +int OsSemUnlink(const char* name) { + return sem_unlink(name); +} + +int OsSemWait(sem_t* sem) { + return sem_wait(sem); +} + +int OsSemPost(sem_t* sem) { + return sem_post(sem); +} + +int OsSemTryWait(sem_t* sem) { + return sem_trywait(sem); +} + +int OsSemGetValue(sem_t* sem, int* value) { + return sem_getvalue(sem, value); +} + +int OsRwLockInitAttr(pthread_rwlockattr_t* attr) { + return pthread_rwlockattr_init(attr); +} + +int OsRwLockDestroyAttr(pthread_rwlockattr_t* attr) { + return pthread_rwlockattr_destroy(attr); +} + +int OsRwLockAttrSetPShared(pthread_rwlockattr_t* attr, int shared) { + return pthread_rwlockattr_setpshared(attr, shared); +} + +int OsRwLockInit(pthread_rwlock_t* rw_lock, const pthread_rwlockattr_t* attr) { + return pthread_rwlock_init(rw_lock, attr); +} + +int OsRwLockTryReadLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_tryrdlock(rw_lock); +} + +int OsRwLockReadLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_rdlock(rw_lock); +} + +int OsRwLockTryWriteLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_trywrlock(rw_lock); +} + +int OsRwLockWriteLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_wrlock(rw_lock); +} + +int OsRwLockUnlock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_unlock(rw_lock); +} + +int OsRwLockDestroyLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_destroy(rw_lock); +} + +int OsClockGetTime(clockid_t clk_id, timespec* tp) { + return clock_gettime(clk_id, tp); +} + +// clang-format off +// RULECHECKER_comment(1, 1, check_max_parameters, "The number of arguments reflects the signature of the underlying POSIX API.", true) +int OsThreadCreate(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg) { + // clang-format on + return pthread_create(thread, attr, start_routine, arg); +} + +int OsThreadJoin(pthread_t thread, void** retval) { + return pthread_join(thread, retval); +} + +EXCLUDE_COVERAGE_END + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/TimeBaseReaderFactory.cpp b/src/tsync-utility-lib/src/TimeBaseReaderFactory.cpp new file mode 100644 index 0000000..a998736 --- /dev/null +++ b/src/tsync-utility-lib/src/TimeBaseReaderFactory.cpp @@ -0,0 +1,21 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/utility/TimeBaseReaderFactory.h" + +#include "SharedMemTimeBaseReader.h" + +namespace score { +namespace time { + +std::unique_ptr TimeBaseReaderFactory::Create(std::string_view name) { + return std::make_unique(name); +} + +std::unique_ptr TimeBaseReaderFactory::Create(std::string_view name, std::size_t max_size) { + return std::make_unique(name, max_size); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/TimeBaseWriterFactory.cpp b/src/tsync-utility-lib/src/TimeBaseWriterFactory.cpp new file mode 100644 index 0000000..1fa5651 --- /dev/null +++ b/src/tsync-utility-lib/src/TimeBaseWriterFactory.cpp @@ -0,0 +1,21 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/utility/TimeBaseWriterFactory.h" + +#include "SharedMemTimeBaseWriter.h" + +namespace score { +namespace time { + +std::unique_ptr TimeBaseWriterFactory::Create(std::string_view name, bool is_owner) { + return std::make_unique(name, is_owner); +} + +std::unique_ptr TimeBaseWriterFactory::Create(std::string_view name, std::size_t max_size, bool is_owner) { + return std::make_unique(name, max_size, is_owner); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/TsyncIdMappingsHandler.cpp b/src/tsync-utility-lib/src/TsyncIdMappingsHandler.cpp new file mode 100644 index 0000000..5b8077b --- /dev/null +++ b/src/tsync-utility-lib/src/TsyncIdMappingsHandler.cpp @@ -0,0 +1,432 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/utility/TsyncIdMappingsHandler.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "score/time/common/Abort.h" +#include "score/time/utility/SysCalls.h" + +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { + +constexpr std::uint64_t kConsumerMappingStartMagic = 0xabcddcbaabcddcba; +constexpr std::uint64_t kProviderMappingStartMagic = 0x1122334444332211; + +TsyncIdMappingsHandler::TsyncIdMappingsHandler(TsyncIdMappingsHandler&& rhs) = default; + +TsyncIdMappingsHandler& TsyncIdMappingsHandler::operator=(TsyncIdMappingsHandler&& rhs) = default; + +TsyncIdMappingsHandler::iterator TsyncIdMappingsHandler::begin() noexcept { + return time_domains_.begin(); +} + +TsyncIdMappingsHandler::const_iterator TsyncIdMappingsHandler::cbegin() const noexcept { + return time_domains_.cbegin(); +} + +TsyncIdMappingsHandler::iterator TsyncIdMappingsHandler::end() noexcept { + return time_domains_.end(); +} + +TsyncIdMappingsHandler::const_iterator TsyncIdMappingsHandler::cend() const noexcept { + return time_domains_.cend(); +} + +bool TsyncIdMappingsHandler::AddDomainMapping(TsyncIdMappingsHandler::TimeDomainId domain_id, + std::string_view domain_name) noexcept { + if (domain_id > kMaxTimebaseId) { + std::cerr << "TsyncIdMappingsHandler::AddDomainMapping(): domain id " << domain_id << " for domain " + << domain_name << " is invalid" << std::endl; + return false; + } else { + return time_domains_.insert({domain_id, domain_name}).second; + } +} + +bool TsyncIdMappingsHandler::AddDomainMapping(const TimeDomainMapping& mapping) noexcept { + if (mapping.first > kMaxTimebaseId) { + return false; + } else { + return time_domains_.insert(mapping).second; + } +} + +bool TsyncIdMappingsHandler::AddConsumerToDomain(std::string_view domain_name, std::string_view consumer_name) noexcept { + auto id = GetDomainId(domain_name); + if (id && *id <= kMaxTimebaseId) { + auto it = consumer_to_time_domain_mappings_.insert( + TimeConsumerToTimeDomainMapping(consumer_name, TimeDomainMapping(*id, domain_name))); + return it.second; + } + + return false; +} + +bool TsyncIdMappingsHandler::AddConsumerToDomain(TsyncIdMappingsHandler::TimeDomainId domain_id, + std::string_view consumer_name) noexcept { + auto domain_name = GetDomainName(domain_id); + if (domain_name) { + auto it = consumer_to_time_domain_mappings_.insert( + TimeConsumerToTimeDomainMapping(consumer_name, TimeDomainMapping(domain_id, *domain_name))); + return it.second; + } + + return false; +} + +std::optional TsyncIdMappingsHandler::GetDomainNameForConsumer(std::string_view consumer_name) { + DoSharedMemoryRead(); + auto it = consumer_to_time_domain_mappings_.find(consumer_name); + if (it != consumer_to_time_domain_mappings_.end()) { + return it->second.second; + } + + return std::nullopt; +} + +std::optional TsyncIdMappingsHandler::GetDomainIdForConsumer( + std::string_view consumer_name) { + DoSharedMemoryRead(); + auto it = consumer_to_time_domain_mappings_.find(consumer_name); + if (it != consumer_to_time_domain_mappings_.end()) { + return it->second.first; + } + return std::nullopt; +} + +bool TsyncIdMappingsHandler::AddProviderToDomain(std::string_view domain_name, std::string_view provider_name) noexcept { + auto id = GetDomainId(domain_name); + if (id && *id <= kMaxTimebaseId) { + auto prov = GetProviderForDomain(*id); + if (!prov) { + auto it = provider_to_time_domain_mappings_.insert( + TimeProviderToTimeDomainMapping(provider_name, TimeDomainMapping(*id, domain_name))); + return it.second; + } + } + + return false; +} + +bool TsyncIdMappingsHandler::AddProviderToDomain(TsyncIdMappingsHandler::TimeDomainId domain_id, + std::string_view provider_name) noexcept { + auto domain_name = GetDomainName(domain_id); + if (domain_name) { + auto prov = GetProviderForDomain(*domain_name); + if (!prov) { + auto it = provider_to_time_domain_mappings_.insert( + TimeProviderToTimeDomainMapping(provider_name, TimeDomainMapping(domain_id, *domain_name))); + return it.second; + } + } + + return false; +} + +std::optional TsyncIdMappingsHandler::GetProviderForDomain(std::string_view domain_name) noexcept { + DoSharedMemoryRead(); + auto res = std::find_if(provider_to_time_domain_mappings_.begin(), provider_to_time_domain_mappings_.end(), + [domain_name](const auto& it) -> bool { return (it.second.second == domain_name); }); + if (res != provider_to_time_domain_mappings_.end()) { + return res->first; + } + return std::nullopt; +} + +std::optional TsyncIdMappingsHandler::GetProviderForDomain( + TsyncIdMappingsHandler::TimeDomainId domain_id) noexcept { + if (domain_id > kMaxTimebaseId) { + return std::nullopt; + } + + DoSharedMemoryRead(); + auto res = std::find_if(provider_to_time_domain_mappings_.begin(), provider_to_time_domain_mappings_.end(), + [domain_id](const auto& it) -> bool { return (it.second.first == domain_id); }); + + if (res != provider_to_time_domain_mappings_.end()) { + return res->first; + } + + return std::nullopt; +} + +std::optional TsyncIdMappingsHandler::GetDomainNameForProvider(std::string_view provider_name) { + DoSharedMemoryRead(); + auto it = provider_to_time_domain_mappings_.find(provider_name); + if (it != provider_to_time_domain_mappings_.end()) { + return it->second.second; + } + return std::nullopt; +} + +std::optional TsyncIdMappingsHandler::GetDomainIdForProvider( + std::string_view provider_name) { + DoSharedMemoryRead(); + auto it = provider_to_time_domain_mappings_.find(provider_name); + if (it != provider_to_time_domain_mappings_.end()) { + return it->second.first; + } + return std::nullopt; +} + +void TsyncIdMappingsHandler::Clear() { + writer_.reset(); + reader_.reset(); + provider_to_time_domain_mappings_.clear(); + consumer_to_time_domain_mappings_.clear(); + time_domains_.clear(); +} + +std::optional TsyncIdMappingsHandler::GetDomainId(std::string_view domain_name) noexcept { + DoSharedMemoryRead(); + for (auto it : time_domains_) { + if (it.second == domain_name) { + return std::optional(it.first); + } + } + return std::nullopt; +} + +std::optional TsyncIdMappingsHandler::GetDomainName(TsyncIdMappingsHandler::TimeDomainId domain_id) noexcept { + if (domain_id <= kMaxTimebaseId) { + DoSharedMemoryRead(); + auto it = time_domains_.find(domain_id); + if (it != time_domains_.end()) { + return std::optional(it->second); + } + } + return std::nullopt; +} + +bool TsyncIdMappingsHandler::AddConsumerToDomainMapping(uint32_t domain_id, std::string_view consumer_name) noexcept { + if (IsEmpty()) { + return false; + } + + auto domain = time_domains_.find(domain_id); + if (domain == time_domains_.end()) { + return false; + } + + std::string_view domain_name(domain->second); + + auto it = consumer_to_time_domain_mappings_.insert( + TimeConsumerToTimeDomainMapping(consumer_name, TimeDomainMapping(domain_id, domain_name))); + return it.second; +} + +bool TsyncIdMappingsHandler::AddProviderToDomainMapping(uint32_t domain_id, std::string_view provider_name) noexcept { + if (IsEmpty()) { + return false; + } + + auto domain = time_domains_.find(domain_id); + if (domain == time_domains_.end()) { + return false; + } + + std::string_view domain_name(domain->second); + + auto it = provider_to_time_domain_mappings_.insert( + TimeProviderToTimeDomainMapping(provider_name, TimeDomainMapping(domain_id, domain_name))); + return it.second; +} + +void TsyncIdMappingsHandler::CommitMappingsToSharedMemory() noexcept { + if (!writer_) { + writer_ = TimeBaseWriterFactory::Create(kIdMappingsShmemFileName, kIdMappingsShmemSize, true); + } + + writer_->GetAccessor().Open(); + + std::lock_guard lock(*writer_); + std::size_t num_domains{time_domains_.size()}; + + if (num_domains > kMaxNumberOfTimeDomains) { + std::stringstream ss; + ss << "TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): " + << "Number of time domains has exceeded the maximum permissible value of " << kMaxNumberOfTimeDomains; + logFatalAndAbort(ss.str().c_str()); + } + + bool res = writer_->Write(static_cast(num_domains)); + + // write time domain mappings + auto it = time_domains_.begin(); + while (res && it != time_domains_.end()) { + res = (writer_->Write(it->first) && writer_->Write(it->second)); + ++it; + } + + if (!res) { + logFatalAndAbort("TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): Error writing time domain mappings."); + } + + // write consumer mappings + std::size_t num_consumer_mappings{consumer_to_time_domain_mappings_.size()}; + + if (num_consumer_mappings > kMaxConsumersPerTimeDomain) { + std::stringstream ss; + ss << "TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): " + << "Number of consumer mappings has exceeded the maximum permissible value of " + << kMaxConsumersPerTimeDomain; + logFatalAndAbort(ss.str().c_str()); + } + + res = (writer_->Write(kConsumerMappingStartMagic) && + writer_->Write(static_cast(num_consumer_mappings))); + + if (!res) { + logFatalAndAbort("TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): Error writing consumer mappings header."); + } + auto consumer_it = consumer_to_time_domain_mappings_.begin(); + while (res && consumer_it != consumer_to_time_domain_mappings_.end()) { + // storing the consumer name and the id of the time domain + res = (writer_->Write(consumer_it->first) && writer_->Write(consumer_it->second.first)); + ++consumer_it; + } + + if (!res) { + logFatalAndAbort("TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): Error writing consumer mappings."); + } + + // write provider mappings + std::size_t num_provider_mappings{provider_to_time_domain_mappings_.size()}; + res = (writer_->Write(kProviderMappingStartMagic) && + num_provider_mappings < std::numeric_limits::max() && + writer_->Write(static_cast(num_provider_mappings))); + + if (!res) { + logFatalAndAbort("TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): Error writing provider mappings header."); + } + + auto provider_it = provider_to_time_domain_mappings_.begin(); + + while (res && provider_it != provider_to_time_domain_mappings_.end()) { + // storing the provider name and the id of the time domain + res = (writer_->Write(provider_it->first) && writer_->Write(provider_it->second.first)); + + ++provider_it; + } + + if (!res) { + logFatalAndAbort("TsyncIdMappingsHandler::CommitMappingsToSharedMemory(): Error writing provider mappings."); + } +} + +void TsyncIdMappingsHandler::DoSharedMemoryRead() noexcept { + if (!IsEmpty()) { + return; + } + + if (!reader_) { + reader_ = TimeBaseReaderFactory::Create(kIdMappingsShmemFileName, kIdMappingsShmemSize); + } + + reader_->GetAccessor().Open(); + { + std::lock_guard lock(*reader_); + std::uint32_t num_domains = 0U; + // coverity[autosar_cpp14_a0_1_1_violation:FALSE] #49860: This value is used subsequently. + bool res = reader_->Read(num_domains); + + if (!res || num_domains == 0U) { + logFatalAndAbort("TsyncIdMappingsHandler::DoSharedMemoryRead(): No domain mappings found."); + } + + while (res && (num_domains-- != 0U)) { + std::string_view sv{}; + TimeDomainId id{}; + res = (reader_->Read(id) && reader_->Read(sv)); + if (res) { + AddDomainMapping(id, sv); + id = 0; + } else { + logFatalAndAbort("TsyncIdMappingsHandler::DoSharedMemoryRead(): Error reading domain mapping."); + } + } + + std::uint64_t magic{0ULL}; + std::uint32_t num_mappings{0U}; + + res = (reader_->Read(magic) && reader_->Read(num_mappings) && (num_mappings > 0u)); + if (res && magic != kConsumerMappingStartMagic) { + logFatalAndAbort("Error reading start of consumer mappings"); + } + + if (!res) { + consumer_to_time_domain_mappings_.clear(); + } else { + while (res && (num_mappings-- != 0U)) { + std::string_view consumer_name; + TimeDomainId domain_id{}; + res = (reader_->Read(consumer_name) && reader_->Read(domain_id)); + if (res) { + res = AddConsumerToDomainMapping(domain_id, consumer_name); + } else { + logFatalAndAbort("TsyncIdMappingsHandler::DoSharedMemoryRead(): Error reading consumer mapping."); + } + } + } + + res = (reader_->Read(magic) && reader_->Read(num_mappings) && (num_mappings > 0u)); + if (magic != kProviderMappingStartMagic) { + logFatalAndAbort("Error reading start of provider mappings"); + } + + while (res && (num_mappings-- != 0U)) { + std::string_view provider_name; + TimeDomainId domain_id{}; + res = (reader_->Read(provider_name) && reader_->Read(domain_id)); + if (res) { + res = AddProviderToDomainMapping(domain_id, provider_name); + if (!res) { + std::stringstream ss; + ss << "TsyncIdMappingsHandler::DoSharedMemoryRead(): " + << "Error adding provider " << provider_name << " to domain " << domain_id; + logFatalAndAbort(ss.str().c_str()); + } + + } else { + logFatalAndAbort("TsyncIdMappingsHandler::DoSharedMemoryRead(): Error reading provider mapping."); + } + } + } +} + +void TsyncIdMappingsHandler::DumpMappings() const { + std::cerr << "Time domains:" << std::endl; + std::cerr << "-------------" << std::endl; + for (auto& it : time_domains_) { + std::cerr << "Domain id: " << it.first << " / Domain name: " << it.second << std::endl << std::endl; + } + + std::cerr << "Consumer to time domain mappings:" << std::endl; + std::cerr << "---------------------------------" << std::endl; + for (auto it : consumer_to_time_domain_mappings_) { + std::cerr << "Consumer name: " << it.first << " / Domain name: " << it.second.second << std::endl << std::endl; + } + + std::cerr << "Provider to time domain mappings:" << std::endl; + std::cerr << "---------------------------------" << std::endl; + for (auto it : provider_to_time_domain_mappings_) { + std::cerr << "Provider name: " << it.first << " / Domain name: " << it.second.second << std::endl; + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/TsyncNamedSemaphore.cpp b/src/tsync-utility-lib/src/TsyncNamedSemaphore.cpp new file mode 100644 index 0000000..db23414 --- /dev/null +++ b/src/tsync-utility-lib/src/TsyncNamedSemaphore.cpp @@ -0,0 +1,135 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/utility/TsyncNamedSemaphore.h" + +#include /* For O_* constants */ +#include /* For mode constants */ + +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/SysCalls.h" + +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { + +TsyncNamedSemaphore::TsyncNamedSemaphore(const std::string& name, OpenMode openMode, bool is_owner) + : is_owner_{is_owner} { + name_ = (name.front() == '/') ? name : std::string("/").append(name); + int oFlags = is_owner ? (O_CREAT | O_EXCL) : 0; + mode_t mode = static_cast(S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP); + sem_ = OsSemOpen(name_.c_str(), oFlags, mode, (openMode == OpenMode::Signaled ? 1u : 0u)); + // clang-format off + // RULECHECKER_comment(1, 1, check_c_style_cast, "Use of C-style cast forced by underlying POSIX API semantics.", true) + if (sem_ == SEM_FAILED) { + // clang-format on + std::stringstream ss; + ss << "TsyncNamedSemaphore::TsyncNamedSemaphore - failed to create semaphore " << name << " with error " + << std::strerror(errno); + + logFatalAndAbort(ss.str().c_str()); + } +} + +TsyncNamedSemaphore::~TsyncNamedSemaphore() { + Destroy(); +} + +TsyncNamedSemaphore::TsyncNamedSemaphore(TsyncNamedSemaphore&& rhs) { + Move(std::forward(rhs)); +} + +TsyncNamedSemaphore& TsyncNamedSemaphore::operator=(TsyncNamedSemaphore&& rhs) { + Destroy(); + Move(std::forward(rhs)); + return *this; +} + +void TsyncNamedSemaphore::lock() noexcept { + while (OsSemWait(sem_) != 0) { + if (errno != EINTR) { + std::stringstream ss; + ss << "TsyncNamedSemaphore::lock() - sem_wait failed for semaphore " << name_ << " with error " + << std::strerror(errno); + + logFatalAndAbort(ss.str().c_str()); + } + } +} + +void TsyncNamedSemaphore::unlock() noexcept { + if (OsSemPost(sem_) != 0) { + std::stringstream ss; + ss << "TsyncNamedSemaphore::unlock() - sem_post failed for semaphore " << name_ << " with error " + << std::strerror(errno); + + logFatalAndAbort(ss.str().c_str()); + } +} + +bool TsyncNamedSemaphore::try_lock() noexcept { + return (OsSemTryWait(sem_) == 0); +} + +int TsyncNamedSemaphore::get_value() const noexcept { + int val; + + if (OsSemGetValue(sem_, &val) != 0) { + // since we currently do not care for blocked processes, + // only for pending signals, indicating errors with -1 + // is acceptable + val = -1; + } + + return val; +} + +void TsyncNamedSemaphore::Move(TsyncNamedSemaphore&& rhs) { + sem_ = rhs.sem_; + is_owner_ = rhs.is_owner_; + name_ = rhs.name_; + + // clang-format off + // RULECHECKER_comment(1, 1, check_c_style_cast, "Use of C-style cast forced by underlying POSIX API semantics.", true) + rhs.sem_ = SEM_FAILED; + // clang-format on +} + +void TsyncNamedSemaphore::Destroy() { + // clang-format off + // RULECHECKER_comment(1, 1, check_c_style_cast, "Use of C-style cast forced by underlying POSIX API semantics.", true) + if (sem_ != SEM_FAILED) { + // clang-format on + if (OsSemClose(sem_) != 0) { + std::stringstream ss; + ss << "TsyncNamedSemaphore::Destroy() - sem_close failed for semaphore " << name_ << " with error " + << std::strerror(errno); + + logFatalAndAbort(ss.str().c_str()); + } + + // clang-format off + // RULECHECKER_comment(1, 1, check_c_style_cast, "Use of C-style cast forced by underlying POSIX API semantics.", true) + sem_ = SEM_FAILED; + // clang-format on + + if (is_owner_) { + if (OsSemUnlink(name_.c_str()) != 0) { + std::stringstream ss; + ss << "TsyncNamedSemaphore::Destroy() - sem_unlink failed for semaphore " << name_ << " with error " + << std::strerror(errno); + + logFatalAndAbort(ss.str().c_str()); + } + } + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/TsyncReadWriteLock.cpp b/src/tsync-utility-lib/src/TsyncReadWriteLock.cpp new file mode 100644 index 0000000..5049520 --- /dev/null +++ b/src/tsync-utility-lib/src/TsyncReadWriteLock.cpp @@ -0,0 +1,106 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "TsyncReadWriteLock.h" + +#include +#include + +#include "score/time/common/Abort.h" + +#include "score/time/utility/SysCalls.h" + +using score::time::common::logFatalAndAbort; + +namespace score { +namespace time { + +TsyncReadWriteLock::TsyncReadWriteLock() : rw_lock_(nullptr) { +} + +TsyncReadWriteLock::~TsyncReadWriteLock() noexcept { + Close(); +} + +void TsyncReadWriteLock::Open(pthread_rwlock_t* rw_lock_addr, LockMode mode, bool is_owner) noexcept { + Close(); + is_owner_ = is_owner; + rw_lock_ = rw_lock_addr; + + if (is_owner_) { + pthread_rwlockattr_t attr; + int res = OsRwLockInitAttr(&attr); + if (res != 0) { + std::stringstream ss; + ss << "TsyncReadWriteLock::TsyncReadWriteLock() - pthreadrw_lock_attr_init failed. [" << std::strerror(res) + << "]"; + logFatalAndAbort(ss.str().c_str()); + } + res = OsRwLockAttrSetPShared(&attr, PTHREAD_PROCESS_SHARED); + if (res != 0) { + std::stringstream ss; + ss << "TsyncReadWriteLock::TsyncReadWriteLock() - pthreadrw_lock_attr_setpshared failed. [" + << std::strerror(res) << "]"; + logFatalAndAbort(ss.str().c_str()); + } + res = OsRwLockInit(rw_lock_, &attr); + if (res != 0) { + std::stringstream ss; + ss << "TsyncReadWriteLock::TsyncReadWriteLock() - pthreadrw_lock_init failed. [" << std::strerror(res) + << "]"; + logFatalAndAbort(ss.str().c_str()); + } + (void)OsRwLockDestroyAttr(&attr); + } else { + } + if (mode == LockMode::Read) { + try_lock_function_ = &OsRwLockTryReadLock; + lock_function_ = &OsRwLockReadLock; + } else { + try_lock_function_ = &OsRwLockTryWriteLock; + lock_function_ = &OsRwLockWriteLock; + } +} + +void TsyncReadWriteLock::Close() noexcept { + if (rw_lock_ && is_owner_) { + OsRwLockDestroyLock(rw_lock_); + rw_lock_ = nullptr; + } +} + +TsyncReadWriteLock::TsyncReadWriteLock(TsyncReadWriteLock&& rhs) : rw_lock_{rhs.rw_lock_}, is_owner_{rhs.is_owner_} { + rhs.rw_lock_ = nullptr; +} + +TsyncReadWriteLock& TsyncReadWriteLock::operator=(TsyncReadWriteLock&& rhs) { + rw_lock_ = rhs.rw_lock_; + is_owner_ = rhs.is_owner_; + + rhs.rw_lock_ = nullptr; + return *this; +} + +bool TsyncReadWriteLock::try_lock() noexcept { + return (try_lock_function_(rw_lock_) == 0); +} + +void TsyncReadWriteLock::lock() noexcept { + int res = lock_function_(rw_lock_); + + if (res != 0) { + logFatalAndAbort("TsyncReadWriteLock::lock() failed."); + } +} + +void TsyncReadWriteLock::unlock() noexcept { + int res = OsRwLockUnlock(rw_lock_); + + if (res != 0) { + logFatalAndAbort("TsyncReadWriteLock::unlock() failed."); + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/src/TsyncReadWriteLock.h b/src/tsync-utility-lib/src/TsyncReadWriteLock.h new file mode 100644 index 0000000..1923ef2 --- /dev/null +++ b/src/tsync-utility-lib/src/TsyncReadWriteLock.h @@ -0,0 +1,42 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_UTILITY_TSYNCREADWRITELOCK_H_ +#define SCORE_TIME_UTILITY_TSYNCREADWRITELOCK_H_ + +#include +#include + +namespace score { +namespace time { + +class TsyncReadWriteLock final { +public: + enum class LockMode { Read, Write }; + + TsyncReadWriteLock(); + TsyncReadWriteLock(const TsyncReadWriteLock&) = delete; + TsyncReadWriteLock& operator=(const TsyncReadWriteLock&) = delete; + TsyncReadWriteLock(TsyncReadWriteLock&&); + TsyncReadWriteLock& operator=(TsyncReadWriteLock&&); + ~TsyncReadWriteLock() noexcept; + + void Open(pthread_rwlock_t* rw_lock_addr, LockMode mode, bool is_owner) noexcept; + void Close() noexcept; + + bool try_lock() noexcept; + void lock() noexcept; + void unlock() noexcept; + +private: + pthread_rwlock_t* rw_lock_{}; + std::function lock_function_{}; + std::function try_lock_function_{}; + bool is_owner_{}; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_UTILITY_TSYNCREADWRITELOCK_H_ diff --git a/src/tsync-utility-lib/src/TsyncSharedUtils.cpp b/src/tsync-utility-lib/src/TsyncSharedUtils.cpp new file mode 100644 index 0000000..1cc369a --- /dev/null +++ b/src/tsync-utility-lib/src/TsyncSharedUtils.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "score/time/utility/TsyncSharedUtils.h" + +#include + +#include "score/time/utility/SysCalls.h" + +using score::time::TsyncNamedSemaphore; + +namespace score { +namespace time { + +std::string TsyncSharedUtils::GetTransmissionSemaphoreName(std::uint32_t time_domain_id) { + const std::string sem_name = {"time_domain_"}; + + return sem_name + std::to_string(static_cast(time_domain_id)); +} + +TsyncNamedSemaphore TsyncSharedUtils::CreateTransmissionSemaphore(std::uint32_t time_domain_id, bool as_owner) { + return TsyncNamedSemaphore(GetTransmissionSemaphoreName(time_domain_id), TsyncNamedSemaphore::OpenMode::Unsignaled, + as_owner); +} + +std::optional TsyncSharedUtils::GetCurrentVirtualLocalTime() { + timespec ts; + if (OsClockGetTime(CLOCK_MONOTONIC, &ts) == -1) { + return std::nullopt; + } + + std::chrono::seconds sec(ts.tv_sec); + std::chrono::nanoseconds nsec(ts.tv_nsec); + std::chrono::nanoseconds sec_in_nsec = std::chrono::duration_cast(sec); + + return (sec_in_nsec + nsec); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/Explicitly_Untested/SysCalls_Not_Tested.cpp b/src/tsync-utility-lib/test/Explicitly_Untested/SysCalls_Not_Tested.cpp new file mode 100644 index 0000000..41481d2 --- /dev/null +++ b/src/tsync-utility-lib/test/Explicitly_Untested/SysCalls_Not_Tested.cpp @@ -0,0 +1,130 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include /* For O_* constants */ +#include +#include + +#include "score/time/utility/SysCalls.h" + +namespace score { +namespace time { + +/// @brief Reasons for excluding SysCalls from testing +/// +/// @defgroup SysCalls_Not_Tested SysCalls Not Tested +/// @verbatim embed:rst:leading-slashes +/// +/// System call wrapper functions are used for mocking in unit tests, it does not make sense to write unit tests for +/// them. The reason is that unit tests are typically used to test the behavior of a specific unit of code in isolation +/// from the rest of the system. In the case of system call wrapper functions, the behavior of the function is largely +/// dependent on the behavior of the underlying system call. As a result, it may not be possible to fully test the +/// behavior of the wrapper function in isolation, without also testing the behavior of the system call it wraps. +/// +/// @endverbatim + +int OsShmOpen(const char* name, int oflag, mode_t mode) { + return shm_open(name, oflag, mode); +} + +int OsShmUnlink(const char* name) { + return shm_unlink(name); +} + +int OsFtruncate(int fd, off_t length) { + return ftruncate(fd, length); +} + +void* OsMmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset) { + return mmap(addr, len, prot, flags, fd, offset); +} + +int OsMunmap(void* addr, size_t len) { + return munmap(addr, len); +} + +int OsClose(int fd) { + return close(fd); +} + +sem_t* OsSemOpen(const char* name, int oflag, ...) { + if ((oflag & O_CREAT) != 0) { + // 2 additional parameters are required + va_list argp; + va_start(argp, oflag); + mode_t mode = va_arg(argp, mode_t); + unsigned value = va_arg(argp, unsigned int); + va_end(argp); + return sem_open(name, oflag, mode, value); + } else { + return sem_open(name, oflag); + } +} + +int OsSemClose(sem_t* sem) { + return sem_close(sem); +} + +int OsSemUnlink(const char* name) { + return sem_unlink(name); +} + +int OsSemWait(sem_t* sem) { + return sem_wait(sem); +} + +int OsSemPost(sem_t* sem) { + return sem_post(sem); +} + +int OsSemTryWait(sem_t* sem) { + return sem_trywait(sem); +} + +int OsSemGetValue(sem_t* sem, int* value) { + return sem_getvalue(sem, value); +} + +int OsRwLockInitAttr(pthread_rwlockattr_t* attr) { + return pthread_rwlockattr_init(attr); +} + +int OsRwLockDestroyAttr(pthread_rwlockattr_t* attr) { + return pthread_rwlockattr_destroy(attr); +} + +int OsRwLockAttrSetPShared(pthread_rwlockattr_t* attr, int shared) { + return pthread_rwlockattr_setpshared(attr, shared); +} + +int OsRwLockInit(pthread_rwlock_t* rw_lock, const pthread_rwlockattr_t* attr) { + return pthread_rwlock_init(rw_lock, attr); +} + +int OsRwLockTryReadLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_tryrdlock(rw_lock); +} + +int OsRwLockReadLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_rdlock(rw_lock); +} + +int OsRwLockTryWriteLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_trywrlock(rw_lock); +} + +int OsRwLockWriteLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_wrlock(rw_lock); +} + +int OsRwLockUnlock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_unlock(rw_lock); +} + +int OsRwLockDestroyLock(pthread_rwlock_t* rw_lock) { + return pthread_rwlock_destroy(rw_lock); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/SharedMemTimeBaseReader_UT/BUILD b/src/tsync-utility-lib/test/SharedMemTimeBaseReader_UT/BUILD new file mode 100644 index 0000000..18c95a1 --- /dev/null +++ b/src/tsync-utility-lib/test/SharedMemTimeBaseReader_UT/BUILD @@ -0,0 +1,14 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "SharedMemTimeBaseReader_UT", + srcs = [ + "SharedMemTimeBaseReader_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "@score_baselibs//score/language/futurecpp", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/SharedMemTimeBaseReader_UT/SharedMemTimeBaseReader_UT.cpp b/src/tsync-utility-lib/test/SharedMemTimeBaseReader_UT/SharedMemTimeBaseReader_UT.cpp new file mode 100644 index 0000000..d85e251 --- /dev/null +++ b/src/tsync-utility-lib/test/SharedMemTimeBaseReader_UT/SharedMemTimeBaseReader_UT.cpp @@ -0,0 +1,983 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "score/span.hpp" +#define private public +#include "score/time/utility/TsyncConfigTypes.h" +#include "SharedMemLayout.h" +#include "SharedMemTimeBaseReader.h" +#include "SharedMemTimeBaseWriter.h" +#undef private +#include "matcher_operators.h" +#include "SysCallsReadWriteLockMock.h" +#include "SysCallsShMemMock.h" + +using score::cpp::span; +using namespace score::time; + +class SharedMemTimeBaseReaderTestFixture : public ::testing::Test { +public: + static const int32_t EXIT_CODE; + +protected: + void SetUp() override { + tb_reader_ = std::make_unique(shared_mem_name_); + shared_mem_mock = std::make_unique<::testing::NiceMock>(); + rw_lock_mock = std::make_unique>(); + + // Setup default expectations for success cases + ON_CALL(*shared_mem_mock, OsShmOpen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Return(32768)); + ON_CALL(*shared_mem_mock, OsFtruncate(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*shared_mem_mock, + OsMmap(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Return(mem_region_)); + ON_CALL(*shared_mem_mock, OsClose(::testing::_)).WillByDefault(::testing::Return(0)); + + // install abort handler for our death tests + std::signal(SIGABRT, AbortHandler); + // As we use here singleton mock object, clear expectations after each test + ::testing::Mock::AllowLeak(shared_mem_mock.get()); + } + + void TearDown() override { + tb_reader_.reset(); + shared_mem_mock.reset(); + rw_lock_mock.reset(); + // use the default abort handler again + std::signal(SIGABRT, SIG_DFL); + } + + static void AbortHandler(int /*signal*/) noexcept { + // the mock has to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + shared_mem_mock.reset(); + rw_lock_mock.reset(); + std::exit(EXIT_CODE); + } + + std::string shared_mem_name_ = "/test"; + char mem_region_[4096]; + std::unique_ptr tb_reader_; +}; + +template +class SharedMemTimeBaseReaderReadComplexTypeTestFixture : public SharedMemTimeBaseReaderTestFixture { +protected: + Type struct_; + Type read_data_; +}; + +template +class SharedMemTimeBaseReaderReadConfigTypeTestFixture : public SharedMemTimeBaseReaderTestFixture { +protected: + Type struct_; + Type read_data_; +}; + +template +class SharedMemTimeBaseReaderReadTestFixture : public SharedMemTimeBaseReaderTestFixture { +protected: + Type value_ = 42; + Type read_value_ = 0; +}; + +constexpr const int32_t SharedMemTimeBaseReaderTestFixture::EXIT_CODE = 42; + +using ReadComplexTypes = ::testing::Types; +TYPED_TEST_SUITE(SharedMemTimeBaseReaderReadComplexTypeTestFixture, ReadComplexTypes); + +using ReadConfigTypes = ::testing::Types; +TYPED_TEST_SUITE(SharedMemTimeBaseReaderReadConfigTypeTestFixture, ReadConfigTypes); + +using ReadTypes = + ::testing::Types; +TYPED_TEST_SUITE(SharedMemTimeBaseReaderReadTestFixture, ReadTypes); + +namespace testing { +namespace lib_sharedmemtimebasereader_ut { + +// Test whether Open will open shared memory in case of success case. +TEST_F(SharedMemTimeBaseReaderTestFixture, Open_Succeeds) { + // Act and assert + EXPECT_NO_THROW(tb_reader_->Open()); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, Open_OnMaxSizeTooSmall_Aborts) { + // Arrange + std::size_t *sz = const_cast(&tb_reader_->max_size_); + *sz = sizeof(TsyncReadWriteLock); + + // Act and assert + ASSERT_EXIT( + { + // Act + tb_reader_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "MaxSize is too small"); +} + +// Test whether Open will open shared memory in case of success case. +TEST_F(SharedMemTimeBaseReaderTestFixture, Open_AlreadyOpened_WillNotOpenSharedMemory) { + // Arrange + tb_reader_->Open(); + + EXPECT_CALL(*shared_mem_mock, OsShmOpen(::testing::_, ::testing::_, ::testing::_)).Times(0); + + // Act and assert + tb_reader_->Open(); +} + +// Test whether Open will abort in case if shm_open will fail and returns -1. +TEST_F(SharedMemTimeBaseReaderTestFixture, Open_FailedToOpenSharedMemory_Aborts) { + // Arrange + ASSERT_EXIT( + { + EXPECT_CALL(*shared_mem_mock, OsShmOpen(::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(-1)); + + // Act + tb_reader_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Unable to open shared memory file"); +} + +// Test whether lock will abort if called on a closed reader +TEST_F(SharedMemTimeBaseReaderTestFixture, lock_OnClosedReader_Aborts) { + ASSERT_EXIT({ tb_reader_->lock(); }, ::testing::ExitedWithCode(EXIT_CODE), "lock called on closed reader"); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, lock_OnMaxSizeTooSmall_Aborts) { + tb_reader_->Open(); + std::size_t *max_size = const_cast(&tb_reader_->max_size_); + *max_size = sizeof(TsyncReadWriteLock) - 4u; + ASSERT_EXIT({ tb_reader_->lock(); }, ::testing::ExitedWithCode(EXIT_CODE), "MaxSize is too small"); +} + +// Test whether lock will abort if called on a closed reader +TEST_F(SharedMemTimeBaseReaderTestFixture, unlock_OnClosedReader_Aborts) { + ASSERT_EXIT({ tb_reader_->unlock(); }, ::testing::ExitedWithCode(EXIT_CODE), "unlock called on closed reader"); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, AlignedSkip_BeyondBounds_Fails) { + // Act and assert + EXPECT_NO_THROW(tb_reader_->Open()); + + ASSERT_TRUE(tb_reader_->SetPosition(4094)); + bool res = AlignedSkip(*tb_reader_.get()); + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, Align_OnAlreadyAligned_KeepsPosition) { + tb_reader_->Open(); + Align(*tb_reader_.get()); + std::size_t pos1 = tb_reader_->GetPosition(); + Align(*tb_reader_.get()); + std::size_t pos2 = tb_reader_->GetPosition(); + ASSERT_EQ(pos1, pos2); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, Align_OnUnaligned_AlignsCorrectly) { + tb_reader_->Open(); + ASSERT_TRUE(tb_reader_->Skip(3)); + std::size_t pos = tb_reader_->GetPosition(); + Align(*tb_reader_.get()); + std::size_t pos_aligned = tb_reader_->GetPosition(); + ASSERT_NE(pos, pos_aligned); + ASSERT_EQ(0u, pos_aligned % alignof(uint32_t)); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, Align_OnOutOfBounds_Fails) { + // Act and assert + struct TestStruct { + char a; + char b; + char c; + uint64_t d; + }; + tb_reader_->Open(); + ASSERT_TRUE(tb_reader_->SetPosition(tb_reader_->GetMaxSize() - 2u)); + auto pos_before = tb_reader_->GetPosition(); + ASSERT_FALSE((Align(*tb_reader_))); + auto pos_after = tb_reader_->GetPosition(); + ASSERT_EQ(pos_before, pos_after); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, AlignedSkip_WhenUnaligned_Succeeds) { + // Act and assert + EXPECT_NO_THROW(tb_reader_->Open()); + ASSERT_TRUE(tb_reader_->Skip(1)); + bool res = AlignedSkip(*tb_reader_.get()); + ASSERT_TRUE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, AlignedSkip_Succeeds) { + // Act and assert + tb_reader_->Open(); + + bool res = AlignedSkip(*tb_reader_.get()); + ASSERT_TRUE(res); +} + +// Test whether Open will abort in case if the first call to mmap fails and returns nullptr. +TEST_F(SharedMemTimeBaseReaderTestFixture, Open_MmapProtWriteReturnsMapFailed_Aborts) { + // Arrange + ASSERT_EXIT( + { + EXPECT_CALL(*shared_mem_mock, + OsMmap(::testing::_, ::testing::_, PROT_WRITE, ::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(MAP_FAILED)); + + // Act + tb_reader_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "OsMmap failed"); +} + +// Test whether Open will abort in case if the second call to mmap fails and returns nullptr. +TEST_F(SharedMemTimeBaseReaderTestFixture, Open_MmapProtReadReturnsMapFailed_Aborts) { + // Arrange + ASSERT_EXIT( + { + EXPECT_CALL(*shared_mem_mock, + OsMmap(::testing::_, ::testing::_, PROT_READ, ::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(MAP_FAILED)); + + // Act + tb_reader_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "OsMmap failed"); +} + +// Test whether Close will output message to stderr if munmap fails +TEST_F(SharedMemTimeBaseReaderTestFixture, Close_MunmapFails_OutputsError) { + // Arrange + tb_reader_->Open(); + testing::internal::CaptureStderr(); + EXPECT_CALL(*shared_mem_mock, OsMunmap(::testing::_, ::testing::_)).WillRepeatedly(::testing::Return(-1)); + + // Act + tb_reader_->Close(); + + // Assert + auto output = testing::internal::GetCapturedStderr(); + EXPECT_THAT(output, ::testing::MatchesRegex(".*munmap failed with.*")); +} + +// Test that when calling Close with an invalid internal state, no errors happen +TEST_F(SharedMemTimeBaseReaderTestFixture, Close_WithInvalidState_Succeeds) { + // Arrange + tb_reader_->state_ = ITimeBaseAccessor::State::Open; + + // Act + tb_reader_->Close(); + + // ASSERT + SUCCEED(); +} + +// Test whether GetAccessor will return a valid instance +TEST_F(SharedMemTimeBaseReaderTestFixture, GetAccessor_OnCall_ReturnsValidInstance) { + // Act + auto &acc = tb_reader_->GetAccessor(); + + // Assert + ASSERT_EQ(acc.GetName(), tb_reader_->GetName()); + ASSERT_EQ(acc.GetState(), tb_reader_->GetState()); +} + +// Test that SetPosition will fail when called on closed writer. +TEST_F(SharedMemTimeBaseReaderTestFixture, SetPosition_MemoryNotOpened_Fails) { + // Act + auto res = tb_reader_->SetPosition(10); + + // ASSERT + ASSERT_FALSE(res); +} + +// Test whether GetName will return correct name of shared memory name. +TEST_F(SharedMemTimeBaseReaderTestFixture, GetName_SucessCase_ReturnCorrectName) { + // Act + auto name = tb_reader_->GetName(); + + // Assert + ASSERT_EQ(name, shared_mem_name_); +} + +// Test whether GetState will return correct state of shared memory. +TEST_F(SharedMemTimeBaseReaderTestFixture, GetState_MemoryNotOpened_ReturnCorrectState) { + // Act + auto res = tb_reader_->GetState(); + + // Assert + ASSERT_EQ(res, ITimeBaseAccessor::State::Closed); +} + +// Test whether GetState will return correct state of shared memory after open call. +TEST_F(SharedMemTimeBaseReaderTestFixture, GetState_MemoryOpened_ReturnCorrectState) { + // Arrange + tb_reader_->Open(); + + // Act + auto res = tb_reader_->GetState(); + + // Assert + ASSERT_EQ(res, ITimeBaseAccessor::State::Open); +} + +// Test whether Skip returns false in case memory not opened yet. +TEST_F(SharedMemTimeBaseReaderTestFixture, Skip_MemoryNotOpened_ReturnFalse) { + // Act + auto res = tb_reader_->Skip(10); + + // Assert + ASSERT_EQ(res, false); +} + +// Test whether Skip returns true and increments offset if the memory was opened. +TEST_F(SharedMemTimeBaseReaderTestFixture, Skip_MemoryOpened_IncrementOffset) { + // Arrange + tb_reader_->Open(); + std::size_t initial_offset = sizeof(TsyncReadWriteLock); + std::size_t skip_bytes_count = 10; + uint8_t test = 42; + uint8_t read_data = 0; + mem_region_[initial_offset + skip_bytes_count] = test; + + // Act + auto res = tb_reader_->Skip(skip_bytes_count); + tb_reader_->Read(read_data); + + // Assert + ASSERT_EQ(res, true); + ASSERT_EQ(read_data, test); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, Skip_OnOutOfBounds_ReturnsFalse) { + // Arrange + tb_reader_->Open(); + + // Act + bool result = tb_reader_->Skip(1000000u); + + // Assert + ASSERT_FALSE(result); +} +// Test that reading to a void pointer fails with a closed reader +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadVoidPointer_WithClosedReader_Fails) { + // Arrange + const char *p; + + // Act + auto res = tb_reader_->Read(&p, 1); + + // ASSERT + ASSERT_FALSE(res); +} + +// Test that an empty span can be read successfully +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_EmptyUserData_Succeeds) { + // Arrange + tb_reader_->Open(); + + // set size of span + mem_region_[tb_reader_->GetPosition()] = 0; + + // Act + tb_reader_->lock(); + UserDataView v; + auto res = tb_reader_->Read(v); + tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(v.size(), 0); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_UserDataWithInvalidSize_Fails) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + writer->Write(static_cast(5u)); + + tb_reader_->Open(); + + // Act + UserDataView v; + auto res = tb_reader_->Read(v); + + // Assert + ASSERT_FALSE(res); +} + +// Test that reading a string on a closed reader fails +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_WithClosedReader_Fails) { + // Arrange & Act + std::string str{}; + auto res = tb_reader_->Read(str); + + // Assert + ASSERT_FALSE(res); + ASSERT_TRUE(str.empty()); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_OnStringLengthFieldOutOfounds_Fails) { + // Arrange + tb_reader_->Open(); + std::size_t offset = tb_reader_->GetMaxSize() - 7u; + tb_reader_->SetPosition(offset); + + // Act + std::string str{}; + auto res = tb_reader_->Read(str); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_OnStringDataOutOfounds_Fails) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + std::size_t offset = writer->GetMaxSize() - 18u; + constexpr uint64_t str_len = 11; + writer->SetPosition(offset); + ASSERT_TRUE(writer->Write(str_len)); + + tb_reader_->Open(); + tb_reader_->SetPosition(offset); + + // Act + std::string str{}; + auto res = tb_reader_->Read(str); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_OnStringLengthOutOfounds_Fails) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + uint64_t str_len = writer->GetMaxSize(); + std::size_t offset = writer->GetMaxSize() - str_len - sizeof(str_len); + writer->SetPosition(offset); + ASSERT_TRUE(writer->Write(str_len)); + + tb_reader_->Open(); + tb_reader_->SetPosition(offset); + + // Act + std::string str{}; + auto res = tb_reader_->Read(str); + + // Assert + ASSERT_FALSE(res); +} + +// Test that an empty std::string can be read successfully +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_EmptyString_Succeeds) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + ASSERT_TRUE(writer->Write(std::string())); + + tb_reader_->Open(); + + // Act + std::string str{}; + auto res = tb_reader_->Read(str); + + // Assert + ASSERT_TRUE(res); + ASSERT_TRUE(str.empty()); +} + +// Test that an std::string can be read successfully +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_Succeeds) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + const std::string s("TEST"); + + writer->Open(); + writer->lock(); + writer->Write(s); + writer->unlock(); + + tb_reader_->Open(); + + // Act + tb_reader_->lock(); + std::string str; + auto res = tb_reader_->Read(str); + tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(str, s); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_WithInvalidLength_Fails) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + const std::string s("TEST"); + + writer->Open(); + writer->lock(); + std::size_t pos = writer->GetPosition(); + writer->Write(s); + writer->unlock(); + + // cut off string early + mem_region_[pos + sizeof(uint64_t) + 3] = 0; + + tb_reader_->Open(); + + // Act + tb_reader_->lock(); + std::string str; + auto res = tb_reader_->Read(str); + tb_reader_->unlock(); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_OnOutOfBounds_Fails) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + std::size_t offset = writer->GetMaxSize() - sizeof(uint64_t); + constexpr std::size_t size = 10u; + + writer->Open(); + writer->SetPosition(offset); + writer->Write(size); + + tb_reader_->Open(); + tb_reader_->SetPosition(offset); + + // Act + std::string str; + auto res = tb_reader_->Read(str); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadString_OnOutOfBounds2_Fails) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + std::size_t offset = writer->GetMaxSize() - sizeof(uint64_t); + constexpr std::size_t size = 5u; + writer->Open(); + // write string length at the very end of the shared memory region + writer->SetPosition(offset); + writer->Write(size); + tb_reader_->Open(); + + // Act + std::string s; + // set read offset so the string length can be read successfully + tb_reader_->SetPosition(offset); + auto res = tb_reader_->Read(s); + + // Assert + ASSERT_FALSE(res); +} + +// Test that an empty std::string_view can be read successfully +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_EmptyStringView_Succeeds) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + writer->lock(); + writer->Write(std::string_view()); + writer->unlock(); + tb_reader_->Open(); + + // Act + tb_reader_->lock(); + std::string_view string_view; + auto res = tb_reader_->Read(string_view); + tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_TRUE(string_view.empty()); +} + +// Test that an std::string_view can be read successfully +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_StringView_Succeeds) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + const std::string_view sv("TEST"); + writer->Open(); + writer->lock(); + writer->Write(sv); + writer->unlock(); + tb_reader_->Open(); + + // Act + tb_reader_->lock(); + std::string_view read_sv; + auto res = tb_reader_->Read(read_sv); + tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(sv, read_sv); +} + +// Test that an std::string_view cannot be read on a closed reader +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadStringView_WithClosedReader_Fails) { + // Arrange + + // Act + std::string_view read_sv; + auto res = tb_reader_->Read(read_sv); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadStringView_OnLengthFieldOutOfBounds_Fails) { + // Arrange + tb_reader_->Open(); + tb_reader_->SetPosition(tb_reader_->GetMaxSize() - 1); + + // Act + std::string_view read_sv; + auto res = tb_reader_->Read(read_sv); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadStringView_OnDataOutOfBounds_Fails) { + // Arrange + std::size_t offset = 10u; + + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + writer->lock(); + ASSERT_TRUE(writer->SetPosition(writer->GetMaxSize() - offset)); + // write stringview length at the very end of the shared memory region + ASSERT_TRUE(writer->Write(static_cast(8u))); + writer->unlock(); + tb_reader_->Open(); + tb_reader_->lock(); + // set read offset so the stringview length can be read successfully + ASSERT_TRUE(tb_reader_->SetPosition(tb_reader_->GetMaxSize() - offset)); + ASSERT_EQ(tb_reader_->GetMaxSize() - offset, tb_reader_->GetPosition()); + + std::uint64_t read_size; + ASSERT_TRUE(tb_reader_->Read(read_size)); + ASSERT_EQ(read_size, 8u); + ASSERT_TRUE(tb_reader_->SetPosition(tb_reader_->GetMaxSize() - offset)); + // Act + std::string_view read_sv; + auto res = tb_reader_->Read(read_sv); + tb_reader_->unlock(); + + // Assert + ASSERT_FALSE(res); +} + +// Test that reading a span fails with a closed reader +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadSpan_WithClosedReader_Fails) { + // Arrange + span span; + + // Act + auto res = tb_reader_->Read(span); + + // Assert + ASSERT_FALSE(res); +} + +// Test that ReadField on a closed reader fails +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadField_WithClosedReader_Fails) { + // Arrange + int32_t dat; + + // Act + auto res = tb_reader_->ReadField(dat); + + // Assert + ASSERT_EQ(res, 0); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadProviderConfig_OnOutOfBounds_Fails) { + // Arrange + tb_reader_->Open(); + TsyncProviderConfig cfg; + + std::size_t *max_size = const_cast(&tb_reader_->max_size_); + *max_size = kSharedMemTimebaseProviderConfigOffset - 2; + + // Act + auto res = tb_reader_->Read(cfg); + + // Assert + ASSERT_EQ(res, 0); +} + +// Test that a span can be read successfully +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_Span_Succeeds) { + // Arrange + tb_reader_->Open(); + std::size_t pos = tb_reader_->GetPosition(); + static constexpr uint8_t span_size = 3; + + // set size of span + mem_region_[pos] = span_size; + // set span data + mem_region_[pos + 1] = 1; + mem_region_[pos + 2] = 2; + mem_region_[pos + 3] = 3; + + // Act + tb_reader_->lock(); + span span; + auto res = tb_reader_->Read(span); + tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(span.size(), span_size); + char *p = &mem_region_[pos + 1u]; + + for (auto b : span) { + ASSERT_EQ(static_cast(*p), static_cast(static_cast(b))); + ++p; + } +} + +// Test that a zero copy in place read succeeds +TEST_F(SharedMemTimeBaseReaderTestFixture, Read_ZeroCopy_Succeeds) { + // Arrange + tb_reader_->Open(); + std::size_t pos = tb_reader_->GetPosition(); + + uint8_t data[]{1, 9, 4, 7, 43}; + + std::memcpy(&mem_region_[pos], data, sizeof(data)); + + // Act + tb_reader_->lock(); + const char *p; + auto res = tb_reader_->Read(&p, sizeof(data)); + tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(p, &mem_region_[pos]); + + for (auto b : data) { + ASSERT_EQ(b, *p); + ++p; + } +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadDomainConfig_OnOutOfBounds_Fails) { + // Arrange + tb_reader_->Open(); + auto pos = tb_reader_->GetPosition(); + + std::size_t *max_size = const_cast(&tb_reader_->max_size_); + *max_size = pos + 10u; + + TsyncTimeDomainConfig cfg; + + // Act + auto res = tb_reader_->Read(cfg); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseReaderTestFixture, ReadConsumerConfig_OnOutOfBounds_Fails) { + // Arrange + tb_reader_->Open(); + auto pos = tb_reader_->GetPosition(); + + std::size_t *max_size = const_cast(&tb_reader_->max_size_); + *max_size = pos + 10u; + + TsyncConsumerConfig cfg; + + // Act + auto res = tb_reader_->Read(cfg); + + // Assert + ASSERT_FALSE(res); +} + +// Test whether Read is capable to read plain data types. +TYPED_TEST(SharedMemTimeBaseReaderReadTestFixture, Read_CorrectData_ReturnCorrectData) { + // Arrange + this->tb_reader_->Open(); + std::size_t offset = this->tb_reader_->GetPosition(); + memcpy(&this->mem_region_[offset], &this->value_, sizeof(this->value_)); + + // Act + this->tb_reader_->lock(); + auto res = this->tb_reader_->Read(this->read_value_); + this->tb_reader_->unlock(); + + // Assert + ASSERT_EQ(res, true); + ASSERT_EQ(this->read_value_, this->value_); +} + +// Test whether Read for complex data types returns false if memory was not opened before. +TYPED_TEST(SharedMemTimeBaseReaderReadComplexTypeTestFixture, Read_MemoryNotOpened_ReturnFalse) { + // Act + auto res = this->tb_reader_->Read(this->struct_); + + // Assert + ASSERT_EQ(res, false); +} + +// Test whether Read for complex data types returns true and correct data in case memory was opened before. +TYPED_TEST(SharedMemTimeBaseReaderReadComplexTypeTestFixture, Read_MemoryOpened_ReturnCorrectData) { + // Arrange + auto writer = std::make_unique(this->shared_mem_name_, true); + writer->Open(); + + this->tb_reader_->Open(); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(&this->struct_, 3, sizeof(this->struct_)); +#pragma GCC diagnostic pop + + writer->lock(); + writer->Write(this->struct_); + writer->unlock(); + + // Act + this->tb_reader_->lock(); + auto res = this->tb_reader_->Read(this->read_data_); + this->tb_reader_->unlock(); + + // Assert + ASSERT_EQ(res, true); + ASSERT_EQ(memcmp(&this->read_data_, &this->struct_, sizeof(this->struct_)), 0); +} + +// Test whether Read for complex data types returns false if memory was not opened before. +TYPED_TEST(SharedMemTimeBaseReaderReadConfigTypeTestFixture, Read_MemoryNotOpened_ReturnFalse) { + // Act + auto res = this->tb_reader_->Read(this->struct_); + + // Assert + ASSERT_EQ(res, false); +} + +// Test whether Read for complex data types returns false if a read fails. +TYPED_TEST(SharedMemTimeBaseReaderReadConfigTypeTestFixture, Read_Fails_ReturnFalse) { + // Act + std::size_t &max_size = const_cast(this->tb_reader_->max_size_); + max_size = 10; + auto res = this->tb_reader_->Read(this->struct_); + max_size = kSharedMemPageSize; + + // Assert + ASSERT_EQ(res, false); +} + +void fillConfig(TsyncConsumerConfig &cfg) { + cfg.name = "test"; + cfg.time_slave_config.is_valid = true; + cfg.time_slave_config.follow_up_timeout_value = std::chrono::milliseconds(42); + cfg.time_slave_config.time_leap_future_threshold = std::chrono::milliseconds(42); + cfg.time_slave_config.time_leap_healing_counter = 42U; + cfg.time_slave_config.time_leap_past_threshold = std::chrono::milliseconds(42); +} + +void fillConfig(TsyncProviderConfig &cfg) { + cfg.name = "test"; + cfg.time_master_config.immediate_resume_time = std::chrono::milliseconds(42); + cfg.time_master_config.is_system_wide_global_time_master = true; + cfg.time_master_config.is_valid = true; + cfg.time_master_config.sync_period = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.allow_provider_rate_correction = true; + cfg.time_sync_correction_config.num_rate_corrections_per_measurement_duration = 42U; + cfg.time_sync_correction_config.offset_correction_adaption_interval = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.offset_correction_jump_threshold = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.rate_deviation_measurement_duration = std::chrono::milliseconds(42); +} + +void fillConfig(TsyncEthTimeDomain &cfg) { + cfg.num_fup_data_id_entries = 16; + cfg.vlan_priority = 2; + cfg.message_format = TsyncEthMessageFormat::kIeee8021ASAutosar; + cfg.is_valid = true; +} + +void fillConfig(TsyncTimeDomainConfig &cfg) { + cfg.domain_id = 12; + cfg.debounce_time = std::chrono::milliseconds(1000); + cfg.sync_loss_timeout = std::chrono::milliseconds(1000); + fillConfig(cfg.eth_time_domain); + fillConfig(cfg.consumer_config); + fillConfig(cfg.provider_config); +} + +// Test whether Read for complex data types returns true and correct data in case memory was opened before. +// config Read/Writes implictly set the offsets of the structs in shmem, so this must be handled in a +// separate test fixture +TYPED_TEST(SharedMemTimeBaseReaderReadConfigTypeTestFixture, Read_Succeeds) { + // Arrange + this->tb_reader_->Open(); + fillConfig(this->struct_); + + SharedMemTimeBaseWriter writer(this->tb_reader_->GetName()); + writer.Open(); + writer.lock(); + auto res = writer.Write(this->struct_); + writer.unlock(); + + ASSERT_TRUE(res); + + // Act + this->tb_reader_->lock(); + res = this->tb_reader_->Read(this->read_data_); + this->tb_reader_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(this->read_data_, this->struct_); +} + +TYPED_TEST(SharedMemTimeBaseReaderReadConfigTypeTestFixture, Read_MemoryNotOpened_ReturnsError) { + // Arrange + auto res = this->tb_reader_->Read(this->read_data_); + + // Assert + ASSERT_EQ(res, false); +} + +TEST(Lib_SharedMemTimeBaseReader, Construction_With_MissingLeadingSlashInName_CorrectsName) { + // Arrange + std::string reader_name = "TestReader"; + std::string expected_shm_name = "/" + reader_name; + + // Act + SharedMemTimeBaseReader reader(reader_name); + + // Assert + ASSERT_EQ(reader.shm_name_, expected_shm_name); +} + +} // namespace lib_sharedmemtimebasereader_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/SharedMemTimeBaseWriter_UT/BUILD b/src/tsync-utility-lib/test/SharedMemTimeBaseWriter_UT/BUILD new file mode 100644 index 0000000..20bea2b --- /dev/null +++ b/src/tsync-utility-lib/test/SharedMemTimeBaseWriter_UT/BUILD @@ -0,0 +1,15 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "SharedMemTimeBaseWriter_UT", + srcs = [ + "SharedMemTimeBaseWriter_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/common", + "@score_baselibs//score/language/futurecpp", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/SharedMemTimeBaseWriter_UT/SharedMemTimeBaseWriter_UT.cpp b/src/tsync-utility-lib/test/SharedMemTimeBaseWriter_UT/SharedMemTimeBaseWriter_UT.cpp new file mode 100644 index 0000000..b774f60 --- /dev/null +++ b/src/tsync-utility-lib/test/SharedMemTimeBaseWriter_UT/SharedMemTimeBaseWriter_UT.cpp @@ -0,0 +1,864 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include +#include + +#define private public +#include "SharedMemTimeBaseWriter.h" +#undef private +#include "score/span.hpp" + +#include "score/time/utility/TsyncConfigTypes.h" +#include "matcher_operators.h" +#include "SysCallsReadWriteLockMock.h" +#include "SysCallsShMemMock.h" +#include "SharedMemLayout.h" +#include "SharedMemTimeBaseReader.h" + +using score::cpp::span; + +#ifndef MAP_FAILED +#define MAP_FAILED ((void*)-1) +#endif + +using namespace score::time; + +class SharedMemTimeBaseWriterTestFixture : public ::testing::Test { +public: + static const int32_t EXIT_CODE; + +protected: + void SetUpWriter(const std::string& name) { + shared_mem_name_ = name; + tb_writer_ = std::make_unique(shared_mem_name_); + } + + void SetUp() override { + // Initialize memory region to zero + std::memset(mem_region_, 0, sizeof(mem_region_)); + + tb_writer_ = std::make_unique(shared_mem_name_); + shared_mem_mock = std::make_unique<::testing::NiceMock>(); + rw_lock_mock = std::make_unique>(); + + // Setup default expectations for success cases + ON_CALL(*shared_mem_mock, OsShmOpen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Return(32768)); + ON_CALL(*shared_mem_mock, OsFtruncate(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*shared_mem_mock, + OsMmap(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Return(mem_region_)); + ON_CALL(*shared_mem_mock, OsClose(::testing::_)).WillByDefault(::testing::Return(0)); + + // install abort handler for our death tests + std::signal(SIGABRT, AbortHandler); + // As we use here singleton mock object, clear expectations after each test + ::testing::Mock::AllowLeak(shared_mem_mock.get()); + } + + void TearDown() override { + tb_writer_.reset(); + shared_mem_mock.reset(); + rw_lock_mock.reset(); + // use the default abort handler again + std::signal(SIGABRT, SIG_DFL); + } + + static void AbortHandler(int /*signal*/) noexcept { + // the mock has to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + shared_mem_mock.reset(); + rw_lock_mock.reset(); + std::exit(EXIT_CODE); + } + + std::string shared_mem_name_ = "/test"; + uint8_t mem_region_[4096]; + std::unique_ptr tb_writer_; +}; + +const int32_t SharedMemTimeBaseWriterTestFixture::EXIT_CODE = 42; + +template +class SharedMemTimeBaseWriterComplexWriteTestFixture : public SharedMemTimeBaseWriterTestFixture { +protected: + Type struct_; + Type* mem_ = reinterpret_cast(reinterpret_cast(&this->mem_region_) + sizeof(pthread_rwlock_t)); +}; + +template +class SharedMemTimeBaseWriterConfigWriteTestFixture : public SharedMemTimeBaseWriterTestFixture { +protected: + Type written_struct_; + Type read_struct_; +}; + +template +class SharedMemTimeBaseWriterWriteTestFixture : public SharedMemTimeBaseWriterTestFixture { +protected: + Type value_ = 42; + Type* mem_ = reinterpret_cast(reinterpret_cast(&this->mem_region_) + + sizeof(score::time::TsyncReadWriteLock)); +}; + +using WriteTypes = + ::testing::Types; +TYPED_TEST_SUITE(SharedMemTimeBaseWriterWriteTestFixture, WriteTypes); + +using WriteComplexTypes = ::testing::Types; +TYPED_TEST_SUITE(SharedMemTimeBaseWriterComplexWriteTestFixture, WriteComplexTypes); + +using WriteConfigTypes = ::testing::Types; +TYPED_TEST_SUITE(SharedMemTimeBaseWriterConfigWriteTestFixture, WriteConfigTypes); + +namespace testing { +namespace lib_sharedmemtimebasewriter_ut { + +// Test whether Open will open shared memory in case of success case. +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_HappyPath_Succeeds) { + // Act and assert + tb_writer_->Open(); + ASSERT_EQ(tb_writer_->GetState(), ITimeBaseAccessor::State::Open); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_OnMaxSizeTooSmall_Aborts) { + // Arrange + size_t* max_size = const_cast(&tb_writer_->max_size_); + *max_size = sizeof(TsyncReadWriteLock); + + ASSERT_EXIT( + { + // Act + tb_writer_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "MaxSize is too small"); +} + +// Test whether Unlink executes correctly if shmem is owned. +TEST_F(SharedMemTimeBaseWriterTestFixture, Close_WhenOwning_CallsUnlink) { + // Arrange + tb_writer_->is_owner_ = true; + tb_writer_->Open(); + + EXPECT_CALL(*shared_mem_mock, OsShmUnlink(::testing::_)).Times(1); + + // Act + tb_writer_->Close(); +} + +// Test whether Unlink executes correctly if shmem is owned. +TEST_F(SharedMemTimeBaseWriterTestFixture, Close_WhenOpenAndAddressIsNull_Succeeds) { + // Arrange + tb_writer_->is_owner_ = true; + tb_writer_->Open(); + tb_writer_->addr_ = nullptr; + + EXPECT_CALL(*shared_mem_mock, OsMunmap(::testing::_, ::testing::_)).Times(0); + + // Act + tb_writer_->Close(); +} + +// Test whether Unlink executes correctly if OsShmUnlink fails. +TEST_F(SharedMemTimeBaseWriterTestFixture, Unlink_OnFailure_Succeeds) { + // Arrange + std::shared_ptr p_tb_writer = + std::make_shared("TestWriter/"); + p_tb_writer->is_owner_ = true; + EXPECT_CALL(*shared_mem_mock, OsShmUnlink(::testing::_)).Times(1).WillOnce(Return(1)); + + // Act + tb_writer_->Unlink(); +} + +// Test whether Open will open shared memory in case of success case if the name has a trailing /. +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_SuccessCase_OpenSharedMemWithTrailingSlash) { + // Act and assert + SetUpWriter(shared_mem_name_ + "/"); + EXPECT_NO_THROW(tb_writer_->Open()); +} + +// Test whether WriteDefaults will succeed +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteDefaults_Succeeds) { + // Act + tb_writer_->Open(); + auto res = tb_writer_->WriteDefaults(); + + // Assert + ASSERT_TRUE(res); +} + +// Test whether WriteDefaults will succeed +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteDefaults_OnCloseddWriter_Fails) { + // Act + auto res = tb_writer_->WriteDefaults(); + + // Assert + ASSERT_FALSE(res); +} + +// Test whether Open will open shared memory in case of success case. +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_AlreadyOpened_WillNotOpenSharedMemory) { + // Arrange + + tb_writer_->Open(); + ASSERT_TRUE(tb_writer_->GetState() == ITimeBaseAccessor::State::Open); + + EXPECT_CALL(*shared_mem_mock, OsShmOpen(::testing::_, ::testing::_, ::testing::_)).Times(0); + + // Act and assert + tb_writer_->Open(); +} + +// Test whether Open will abort in case if OsShmOpen will fail and return -1. +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_FailedToOpenSharedMemory_Aborts) { + // Arrange + ASSERT_EXIT( + { + EXPECT_CALL(*shared_mem_mock, OsShmOpen(::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(-1)); + + // Act + tb_writer_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Unable to open shared memory file"); +} + +// Test whether Open will abort in case if ftruncate fails and returns -1. +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_FailedTruncate_Aborts) { + // Arrange + ASSERT_EXIT( + { + EXPECT_CALL(*shared_mem_mock, OsFtruncate(::testing::_, ::testing::_)).WillOnce(::testing::Return(-1)); + + // act + // OsFtruncate is only called for the owner + tb_writer_->is_owner_ = true; + tb_writer_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Unable to set shared memory block size"); +} + +// Test whether Open will abort in case if mmap fails returns nullptr. +TEST_F(SharedMemTimeBaseWriterTestFixture, Open_MmapReturnsMapFailed_Aborts) { + // Arrange + ASSERT_EXIT( + { + EXPECT_CALL(*shared_mem_mock, + OsMmap(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(MAP_FAILED)); + + // act + tb_writer_->Open(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "OsMmap failed"); +} + +// Test whether lock will abort when called on a closed writer. +TEST_F(SharedMemTimeBaseWriterTestFixture, lock_OnClosedWriter_Aborts) { + // Arrange + ASSERT_EXIT({ tb_writer_->lock(); }, ::testing::ExitedWithCode(EXIT_CODE), "lock called on closed writer"); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, lock_OnMaxSizeTooSmall_Aborts) { + // Arrange + tb_writer_->Open(); + size_t* max_size = const_cast(&tb_writer_->max_size_); + *max_size = 10u; + ASSERT_EXIT({ tb_writer_->lock(); }, ::testing::ExitedWithCode(EXIT_CODE), "MaxSize is too small"); +} + +// Test whether unlock will abort when called on a closed writer. +TEST_F(SharedMemTimeBaseWriterTestFixture, unlock_OnClosedWriter_Aborts) { + // Arrange + ASSERT_EXIT({ tb_writer_->unlock(); }, ::testing::ExitedWithCode(EXIT_CODE), "unlock called on closed writer"); +} + +// Test whether Close will output error in case munmap fails. +TEST_F(SharedMemTimeBaseWriterTestFixture, Close_MunmapReturnsError_OutputsMessage) { + // Arrange + tb_writer_->Open(); + testing::internal::CaptureStderr(); + EXPECT_CALL(*shared_mem_mock, OsMunmap(::testing::_, ::testing::_)).WillOnce(::testing::Return(-1)); + + // Act + tb_writer_->Close(); + + // Assert + auto output = testing::internal::GetCapturedStderr(); + EXPECT_THAT(output, ::testing::MatchesRegex(".*munmap failed with.*")); +} + +// Test whether Close will succeed if Open was not called previously +TEST_F(SharedMemTimeBaseWriterTestFixture, Close_WithoutOpenCall_Succeeds) { + // Arranged by fixture + + // Act + tb_writer_->Close(); + + // Assert + SUCCEED(); +} + +// Test whether GetName will return correct name of shared memory name. +TEST_F(SharedMemTimeBaseWriterTestFixture, GetName_SuccessCase_ReturnCorrectName) { + // Act + auto name = tb_writer_->GetName(); + + // Assert + ASSERT_EQ(name, shared_mem_name_); +} + +// Test whether GetAccessor will return a valid instance +TEST_F(SharedMemTimeBaseWriterTestFixture, GetAccessor_OnCall_ReturnsValidInstance) { + // Act + auto& acc = tb_writer_->GetAccessor(); + + // Assert + ASSERT_EQ(acc.GetName(), tb_writer_->GetName()); + ASSERT_EQ(acc.GetState(), tb_writer_->GetState()); +} + +// Test that writing of an empty byte span will succeed. +TEST_F(SharedMemTimeBaseWriterTestFixture, Write_EmptyByteSpan_Succeeds) { + // Arrange + tb_writer_->Open(); + + // Act + tb_writer_->lock(); + auto pos = tb_writer_->GetPosition(); + auto res = tb_writer_->Write(span()); + tb_writer_->unlock(); + + // Assert + ASSERT_TRUE(res); + + uint8_t* p = &mem_region_[pos]; + // compare size + ASSERT_EQ(static_cast(*p), 0); +} + +// Test that writing a byte span to a closed writer will fail +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteSpan_ToClosedWriter_Fails) { + // Act + ASSERT_EQ(ITimeBaseAccessor::State::Closed, tb_writer_->GetState()); + auto res = tb_writer_->Write(span()); + + // Assert + ASSERT_FALSE(res); +} + +// Test that writing an std::string to a closed writer will fail +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteString_ToClosedWriter_Fails) { + // Act + auto res = tb_writer_->Write(std::string()); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteString_OnLengthFieldOutOfBounds_Fails) { + // Arrange + tb_writer_->Open(); + tb_writer_->SetPosition(tb_writer_->GetMaxSize() - 1u); + + // Act + auto res = tb_writer_->Write(std::string{}); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteString_OnStringDataOutOfBounds_Fails) { + // Arrange + tb_writer_->Open(); + tb_writer_->SetPosition(tb_writer_->GetMaxSize() - 12u); + std::string test{"testtesttestestest"}; + + // Act + auto res = tb_writer_->Write(test); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteString_OnTerminatorOutOfBounds_Fails) { + // Arrange + tb_writer_->Open(); + tb_writer_->SetPosition(tb_writer_->GetMaxSize() - 20u); + Align(*tb_writer_.get()); + std::size_t space_left = tb_writer_->GetMaxSize() - tb_writer_->GetPosition() - sizeof(std::uint64_t); + + ASSERT_NE(space_left, 0u); + + std::string test{"testtesttesttesttesttesttest"}; + + // Act + auto res = tb_writer_->Write(test.substr(space_left)); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteStringView_ToClosedWriter_Fails) { + // Act + auto res = tb_writer_->Write(std::string_view()); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteStringView_ToOpenWriter_Succeeds) { + // Act + tb_writer_->Open(); + auto res = tb_writer_->Write(std::string_view("Test")); + // Assert + ASSERT_TRUE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteStringView_OnSizeFieldOutOfBounds_Fails) { + // Act + tb_writer_->Open(); + tb_writer_->SetPosition(tb_writer_->GetMaxSize() - 2u); + auto res = tb_writer_->Write(std::string_view("Test")); + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteStringView_OnDataOutOfBounds_Fails) { + // Act + tb_writer_->Open(); + tb_writer_->SetPosition(tb_writer_->GetMaxSize() - 20u); + auto res = tb_writer_->Write(std::string_view("TestTestTestTestTestTestTestTest")); + // Assert + ASSERT_FALSE(res); +} + +// Test that writing the consumer config to a closed writer fails. +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteConsumerConfig_ToClosedWriter_Fails) { + // arrange + TsyncConsumerConfig cfg; + + // act + auto res = tb_writer_->Write(cfg); + + // assert + ASSERT_FALSE(res); +} + +// Test that writing to a closed writer fails. +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteProviderConfig_ToClosedWriter_Fails) { + // arrange + TsyncProviderConfig cfg; + + // act + auto res = tb_writer_->Write(cfg); + + // assert + ASSERT_FALSE(res); +} + +// Test that writing of an non-empty byte span will succeed. +TEST_F(SharedMemTimeBaseWriterTestFixture, + Write_ByteSpan_Succeeds) { // Arrange + constexpr auto span_data = make_array(1, 2, 3); + tb_writer_->Open(); + + // Act + tb_writer_->lock(); + auto pos = tb_writer_->GetPosition(); + auto res = tb_writer_->Write(span(span_data)); + tb_writer_->unlock(); + + // Assert + ASSERT_TRUE(res); + + // compare size + ASSERT_EQ(static_cast(mem_region_[pos]), sizeof(span_data)); + // compare bytes + for (auto b : span_data) { + ASSERT_EQ(static_cast(mem_region_[++pos]), static_cast(static_cast(b))); + } +} // namespace lib_sharedmemtimebasewriter_ut + +// Test that SetPosition will fail when called on closed writer. +TEST_F(SharedMemTimeBaseWriterTestFixture, SetPosition_MemoryNotOpened_Fails) { + // Act + auto res = tb_writer_->SetPosition(10); + + // ASSERT + ASSERT_FALSE(res); +} + +// Test whether GetState will return correct state of shared memory. +TEST_F(SharedMemTimeBaseWriterTestFixture, GetState_MemoryNotOpened_ReturnCorrectState) { + // Act + auto res = tb_writer_->GetState(); + + // Assert + ASSERT_EQ(res, ITimeBaseAccessor::State::Closed); +} + +// Test whether GetState will return correct state of shared memory after open call. +TEST_F(SharedMemTimeBaseWriterTestFixture, GetState_MemoryOpened_ReturnCorrectState) { + // Arrange + tb_writer_->Open(); + + // Act + auto res = tb_writer_->GetState(); + + // Assert + ASSERT_EQ(res, ITimeBaseAccessor::State::Open); +} + +// Test whether Write writes correct time stamp data and returns true. +TEST_F(SharedMemTimeBaseWriterTestFixture, Write_ValidTimeStampData_ReturnTrue) { + // Arrange + auto reader = std::make_unique(this->shared_mem_name_); + TimestampWithStatus data = {SynchronizationStatus::kSynchronized, std::chrono::nanoseconds(12), + std::chrono::seconds(34)}; + tb_writer_->Open(); + + // Act + tb_writer_->lock(); + auto res = tb_writer_->Write(data); + tb_writer_->unlock(); + + // Assert + ASSERT_EQ(res, true); + + TimestampWithStatus read_data; + reader->Open(); + reader->lock(); + reader->Read(read_data); + reader->unlock(); + + ASSERT_EQ(read_data.status, data.status); + ASSERT_EQ(read_data.nanoseconds, data.nanoseconds); + ASSERT_EQ(read_data.seconds, data.seconds); +} + +// Test whether Write writes correct virtual local time and returns true. +TEST_F(SharedMemTimeBaseWriterTestFixture, Write_WithValidVirtualLocaltime_Succeeds) { + // Arrange + auto reader = std::make_unique(this->shared_mem_name_); + VirtualLocalTime data(1234); + tb_writer_->Open(); + + // Act + tb_writer_->lock(); + auto res = tb_writer_->Write(data); + tb_writer_->unlock(); + + // Assert + ASSERT_EQ(res, true); + + VirtualLocalTime read_data; + reader->Open(); + reader->lock(); + reader->Read(read_data); + reader->unlock(); + + ASSERT_EQ(data, read_data); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, Write_WithZeroLengthRawData_Succeeds) { + // Arrange + auto reader = std::make_unique(this->shared_mem_name_); + tb_writer_->Open(); + + // Act + tb_writer_->lock(); + auto res = tb_writer_->Write(nullptr, 0u); + tb_writer_->unlock(); + + // Assert + ASSERT_TRUE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, Write_WithOutOfBoudsRawData_Succeeds) { + // Arrange + auto reader = std::make_unique(this->shared_mem_name_); + tb_writer_->Open(); + + // Act + tb_writer_->lock(); + tb_writer_->SetPosition(kSharedMemPageSize - 2u); + auto res = tb_writer_->Write(nullptr, 4u); + tb_writer_->unlock(); + + // Assert + ASSERT_FALSE(res); +} + +// Test whether Skip returns false in case memory not opened yet. +TEST_F(SharedMemTimeBaseWriterTestFixture, Skip_OnClosedWriter_ReturnFalse) { + // Act + auto res = tb_writer_->Skip(10); + + // Assert + ASSERT_EQ(res, false); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, Skip_OnOutOfBounds_ReturnFalse) { + // Arrange + tb_writer_->Open(); + tb_writer_->SetPosition(tb_writer_->GetMaxSize() - 4u); + + // Act + auto res = tb_writer_->Skip(10); + + // Assert + ASSERT_EQ(res, false); +} + +// Test whether Skip returns true and increments offset if the memory was opened. +TEST_F(SharedMemTimeBaseWriterTestFixture, Skip_OnOpenWriter_IncrementOffset) { + // Arrange + tb_writer_->Open(); + std::size_t initial_offset = sizeof(score::time::TsyncReadWriteLock); + std::size_t skip_bytes_count = 10; + uint8_t test = 42; + + // Act + tb_writer_->lock(); + auto res = tb_writer_->Skip(skip_bytes_count); + ASSERT_EQ(initial_offset + skip_bytes_count, tb_writer_->GetPosition()); + tb_writer_->Write(test); + tb_writer_->unlock(); + + // Assert + ASSERT_EQ(res, true); + ASSERT_EQ(mem_region_[initial_offset + skip_bytes_count], test); +} + +// Test whether Read is capable to read plain data types. +TYPED_TEST(SharedMemTimeBaseWriterWriteTestFixture, Write_Succeeds) { + // Arrange + this->tb_writer_->Open(); + + // Act + this->tb_writer_->lock(); + auto res = this->tb_writer_->Write(this->value_); + this->tb_writer_->unlock(); + + // Assert + ASSERT_TRUE(res); + ASSERT_EQ(this->value_, *(this->mem_)); +} + +TYPED_TEST(SharedMemTimeBaseWriterWriteTestFixture, Write_OnClosedWriter_Fails) { + // Arrange + this->tb_writer_->Close(); + + // Act + auto res = this->tb_writer_->Write(this->value_); + + // Assert + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteUserData_OnClosedWriter_Fails) { + // Arrange + auto p_tb_writer = std::make_shared(this->shared_mem_name_); + UserData data; + data[0] = std::byte{0}; + + auto res = p_tb_writer->Write(data); + + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteUserData_OnWriteFieldFailure_Fails) { + // Arrange + auto tb_writer = std::make_shared(this->shared_mem_name_); + tb_writer->SetPosition(kSharedMemPageSize - 2); + + UserData data; + data[0] = std::byte{3}; + data[1] = std::byte{0}; + data[2] = std::byte{0}; + data[3] = std::byte{0}; + + auto res = tb_writer->Write(data); + + ASSERT_FALSE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteUserData_OnIllegalSize_Succeeds) { + // Arrange + auto tb_writer = std::make_shared(this->shared_mem_name_); + + constexpr auto data = make_array(1, 2, 3, 4, 5); + tb_writer->Open(); + tb_writer->lock(); + + // act + auto res = tb_writer->Write(UserDataView{data}); + + // assert + ASSERT_TRUE(res); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteUserData_OnOutOfBounds_Fails) { + // Arrange + auto tb_writer = std::make_shared(this->shared_mem_name_); + + constexpr auto data = make_array(1, 2, 3, 4, 5); + tb_writer->Open(); + tb_writer->lock(); + tb_writer->SetPosition(kSharedMemPageSize - 2u); + + // act + auto res = tb_writer->Write(UserDataView{data}); + + // assert + ASSERT_FALSE(res); +} + +// Test whether Read for complex data types returns false if memory was not opened before. +TYPED_TEST(SharedMemTimeBaseWriterComplexWriteTestFixture, Write_Succeeds) { + // Arrange + auto reader = std::make_unique(this->shared_mem_name_); + this->tb_writer_->Open(); + this->struct_ = TypeParam{}; + + // Act + this->tb_writer_->lock(); + auto res = this->tb_writer_->Write(this->struct_); + ASSERT_TRUE(res); + this->tb_writer_->unlock(); + + decltype(this->struct_) read_data{}; + reader->Open(); + reader->lock(); + res = reader->Read(read_data); + ASSERT_TRUE(res); + reader->unlock(); + + // Assert + ASSERT_EQ(memcmp(&read_data, &this->struct_, sizeof(this->struct_)), 0); +} + +void fillConfig(TsyncConsumerConfig& cfg) { + cfg.name = "test"; + cfg.time_slave_config.follow_up_timeout_value = std::chrono::milliseconds(0xf1); + cfg.time_slave_config.time_leap_future_threshold = std::chrono::milliseconds(0xf2); + cfg.time_slave_config.time_leap_past_threshold = std::chrono::milliseconds(0xf3); + cfg.time_slave_config.time_leap_healing_counter = 0xf4; + cfg.time_slave_config.global_time_sequence_counter_jump_width = 0xf5; + cfg.time_slave_config.sub_tlv_config.time_enabled = true; + cfg.time_slave_config.sub_tlv_config.status_enabled = false; + cfg.time_slave_config.sub_tlv_config.user_data_enabled = true; + cfg.time_slave_config.is_valid = true; +} + +void fillConfig(TsyncProviderConfig& cfg) { + cfg.name = "test"; + cfg.time_master_config.immediate_resume_time = std::chrono::milliseconds(42); + cfg.time_master_config.is_system_wide_global_time_master = true; + cfg.time_master_config.is_valid = true; + cfg.time_master_config.sync_period = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.num_rate_corrections_per_measurement_duration = 42U; + cfg.time_sync_correction_config.offset_correction_adaption_interval = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.offset_correction_jump_threshold = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.rate_deviation_measurement_duration = std::chrono::milliseconds(42); + cfg.time_sync_correction_config.allow_provider_rate_correction = true; + cfg.time_sync_correction_config.is_valid = true; +} + +void fillConfig(TsyncEthTimeDomain& cfg) { + cfg.num_fup_data_id_entries = 16; + cfg.vlan_priority = 2; + cfg.message_format = TsyncEthMessageFormat::kIeee8021ASAutosar; + cfg.is_valid = true; +} + +void fillConfig(TsyncTimeDomainConfig& cfg) { + cfg.domain_id = 12; + cfg.debounce_time = std::chrono::milliseconds(1000); + cfg.sync_loss_timeout = std::chrono::milliseconds(1000); + fillConfig(cfg.eth_time_domain); + fillConfig(cfg.consumer_config); + fillConfig(cfg.provider_config); +} + +TYPED_TEST(SharedMemTimeBaseWriterConfigWriteTestFixture, Write_Succeeds) { + // Arrange + fillConfig(this->written_struct_); + + // Act + this->tb_writer_->Open(); + this->tb_writer_->lock(); + auto res_write = this->tb_writer_->Write(this->written_struct_); + this->tb_writer_->unlock(); + + SharedMemTimeBaseReader reader(this->tb_writer_->GetName()); + reader.Open(); + reader.lock(); + auto res_read = reader.Read(this->read_struct_); + reader.unlock(); + + // Assert + ASSERT_TRUE(res_write); + ASSERT_TRUE(res_read); + ASSERT_EQ(this->read_struct_, this->written_struct_); +} + +TYPED_TEST(SharedMemTimeBaseWriterConfigWriteTestFixture, Write_OnPositionEof_Fails) { + // Arrange + fillConfig(this->written_struct_); + + // Act + this->tb_writer_->Open(); + this->tb_writer_->lock(); + std::size_t& max_size_ref = const_cast(this->tb_writer_->max_size_); + max_size_ref = 0; + auto res_write = this->tb_writer_->Write(this->written_struct_); + this->tb_writer_->unlock(); + + // Assert + ASSERT_FALSE(res_write); +} + +TYPED_TEST(SharedMemTimeBaseWriterConfigWriteTestFixture, Write_OnClosed_Fails) { + // Arrange + fillConfig(this->written_struct_); + this->tb_writer_->Close(); + + // Act + auto res_write = this->tb_writer_->Write(this->written_struct_); + + // Assert + ASSERT_FALSE(res_write); +} + +TEST(Lib_SharedMemTimeBaseWriter, Construction_With_MissingLeadingSlashInName_CorrectsName) { + // Arrange + std::string writer_name = "TestWriter"; + std::string corrected_writer_name = "/" + writer_name; + + // Act + SharedMemTimeBaseWriter writer(writer_name); + + // Assert + ASSERT_EQ(writer.shm_name_, corrected_writer_name); +} + +TEST_F(SharedMemTimeBaseWriterTestFixture, WriteField_OnOutOfBounds_Fails) { + // Arrange + std::size_t max_size = 100u; + auto tb_writer = std::make_shared(this->shared_mem_name_, max_size); + uint32_t data{10}; + tb_writer->Open(); + tb_writer->SetPosition(max_size - 1u); + auto res = tb_writer->WriteField(data); + ASSERT_EQ(res, 0); +} + +} // namespace lib_sharedmemtimebasewriter_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/TimeBaseAccessorFactory_UT/BUILD b/src/tsync-utility-lib/test/TimeBaseAccessorFactory_UT/BUILD new file mode 100644 index 0000000..ead9a62 --- /dev/null +++ b/src/tsync-utility-lib/test/TimeBaseAccessorFactory_UT/BUILD @@ -0,0 +1,12 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TimeBaseAccessorFactory_UT", + srcs = [ + "TimeBaseAccessorFactory_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib:tsync_utility", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/TimeBaseAccessorFactory_UT/TimeBaseAccessorFactory_UT.cpp b/src/tsync-utility-lib/test/TimeBaseAccessorFactory_UT/TimeBaseAccessorFactory_UT.cpp new file mode 100644 index 0000000..a5b0e2b --- /dev/null +++ b/src/tsync-utility-lib/test/TimeBaseAccessorFactory_UT/TimeBaseAccessorFactory_UT.cpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "score/time/utility/TimeBaseWriterFactory.h" + +using namespace score::time; + +namespace testing { +namespace timebaseaccessorfactory_ut { + +TEST(Lib_TimeBaseAccessorFactory_TestSuite, TimeBaseAccessorFactory_Create_Function_ReadWrite_Success) { + // Set up writer + auto writer = TimeBaseWriterFactory::Create("/time_domain1", true); + ASSERT_NE(writer, nullptr); + + // Writing something to the shared-mem file + writer->GetAccessor().Open(); + const uint64_t write_value = 0xb0b; + bool res = writer->Write(write_value); + EXPECT_TRUE(res); + + // Setup Reader + auto reader = TimeBaseReaderFactory::Create("/time_domain1"); + ASSERT_NE(reader, nullptr); + + reader->GetAccessor().Open(); + uint64_t read_value; + res = reader->Read(read_value); + EXPECT_TRUE(res); + + // Assert if read and write result are equal + EXPECT_EQ(read_value, write_value); +} + +} // namespace timebaseaccessorfactory_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/TsyncIdMappingsHandler_UT/BUILD b/src/tsync-utility-lib/test/TsyncIdMappingsHandler_UT/BUILD new file mode 100644 index 0000000..d0f89cc --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncIdMappingsHandler_UT/BUILD @@ -0,0 +1,17 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TsyncIdMappingsHandler_UT", + srcs = [ + "TsyncIdMappingsHandler_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/tsync-utility-lib:utility_tsync_id_mappings_handler", + "//src/common", + "@score_baselibs//score/language/futurecpp", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/TsyncIdMappingsHandler_UT/TsyncIdMappingsHandler_UT.cpp b/src/tsync-utility-lib/test/TsyncIdMappingsHandler_UT/TsyncIdMappingsHandler_UT.cpp new file mode 100644 index 0000000..91e1fad --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncIdMappingsHandler_UT/TsyncIdMappingsHandler_UT.cpp @@ -0,0 +1,996 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "score/time/timestamp.h" +#include + +#define private public +#include "score/time/utility/TsyncIdMappingsHandler.h" +#undef private + +#include "SharedMemTimeBaseReaderMock.h" +#include "SharedMemTimeBaseWriterMock.h" +#include "TimeBaseReaderFactoryMock.h" +#include "TimeBaseWriterFactoryMock.h" + +namespace score { +namespace time { +extern std::unique_ptr<::testing::NiceMock> reader_factory_mock; +extern std::unique_ptr<::testing::NiceMock> writer_factory_mock; +} // namespace time +} // namespace score + +using score::time::reader_factory_mock; +using score::time::writer_factory_mock; + +using namespace score::time; +using ::testing::Return; + +constexpr int32_t kNumTimeDomains{4}; +const std::string_view kTimeDomainNames[kNumTimeDomains] = {"domain_1", "domain_2", "domain_3", "domain_4"}; + +const std::string_view kTimeConsumerNames[4] = {"consumer_1", "consumer_2", "consumer_3", "consumer_4"}; + +const std::string_view kTimeProviderNames[2] = { + "provider_1", + "provider_2", +}; + +static constexpr std::uint64_t kConsumerMagic = 0xabcddcbaabcddcba; +static constexpr std::uint64_t kProviderMagic = 0x1122334444332211; + +class TsyncIdMappingsHandlerFixture : public ::testing::Test { +protected: + void SetUp() override { + reader_factory_mock = std::make_unique<::testing::NiceMock>(); + writer_factory_mock = std::make_unique<::testing::NiceMock>(); + uint32_t id = 1; + for (auto sv : kTimeDomainNames) { + mappings_handler_.AddDomainMapping(id++, sv); + } + + mappings_handler_.AddDomainMapping(TsyncIdMappingsHandler::TimeDomainMapping(9, "just_for_coverage")); + } + + void TearDown() override { + mappings_handler_.Clear(); + reader_factory_mock.reset(); + writer_factory_mock.reset(); + } + +public: + TsyncIdMappingsHandler mappings_handler_; + + static constexpr const uint32_t invalid_timebase_id_ = 99; + static constexpr const uint32_t nonexistent_timebase_id_ = 11; + static constexpr const char* invalid_time_base_name_ = "does not exist"; + static constexpr size_t invalid_id_mapping_no_ = 5; +}; + +class TsyncIdMappingsHandlerDeathFixture : public ::testing::Test { +protected: + void SetUp() override { + reader_factory_mock = std::make_unique<::testing::NiceMock>(); + writer_factory_mock = std::make_unique<::testing::NiceMock>(); + + mh_ = std::make_unique(); + mh_->reader_ = TimeBaseReaderFactory::Create(kIdMappingsShmemFileName, kIdMappingsShmemSize); + mh_->writer_ = TimeBaseWriterFactory::Create(kIdMappingsShmemFileName, kIdMappingsShmemSize); + + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + ::testing::Mock::AllowLeak(reader_mock); + // install abort handler for our death tests + std::signal(SIGABRT, AbortHandler); + } + + void TearDown() override { + reader_factory_mock.reset(); + writer_factory_mock.reset(); + mh_.reset(); + // use the default abort handler again + std::signal(SIGABRT, SIG_DFL); + } + + void AddMappings() { + uint32_t id = 1; + for (auto sv : kTimeDomainNames) { + mh_->AddDomainMapping(id++, sv); + } + mh_->AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0]); + } + + static void AbortHandler(int /*signal*/) noexcept { + // the mocks have to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + reader_factory_mock.reset(); + writer_factory_mock.reset(); + mh_.reset(); + std::exit(EXIT_CODE); + } + + // needs to be deleted in the abort handler so the set up expectations + // are applied + static std::unique_ptr mh_; + static constexpr const int32_t EXIT_CODE = 42; +}; + +std::unique_ptr TsyncIdMappingsHandlerDeathFixture::mh_{}; + +namespace testing { +namespace lib_tsyncidmappingshandler_ut { + +TEST_F(TsyncIdMappingsHandlerFixture, Ctor_Succeeds) { + // arrange + TsyncIdMappingsHandler mh; + + // assert + ASSERT_FALSE(mh.reader_); + ASSERT_FALSE(mh.writer_); + ASSERT_TRUE(mh.time_domains_.empty()); + ASSERT_TRUE(mh.provider_to_time_domain_mappings_.empty()); + ASSERT_TRUE(mh.consumer_to_time_domain_mappings_.empty()); +} + +TEST_F(TsyncIdMappingsHandlerFixture, MoveCtor_Succeeds) { + // arrange + TsyncIdMappingsHandler mh1; + TsyncIdMappingsHandler mh2{std::move(mh1)}; + + // assert + ASSERT_FALSE(mh2.reader_); + ASSERT_FALSE(mh2.writer_); + ASSERT_TRUE(mh2.time_domains_.empty()); + ASSERT_TRUE(mh2.provider_to_time_domain_mappings_.empty()); + ASSERT_TRUE(mh2.consumer_to_time_domain_mappings_.empty()); +} + +TEST_F(TsyncIdMappingsHandlerFixture, MoveAssignment_Succeeds) { + // arrange + TsyncIdMappingsHandler mh1; + + // act + mh1 = TsyncIdMappingsHandler(); + + // assert + TsyncIdMappingsHandler mh2; + + ASSERT_EQ(mh1.time_domains_.size(), mh2.time_domains_.size()); + ASSERT_EQ(mh1.consumer_to_time_domain_mappings_.size(), mh2.consumer_to_time_domain_mappings_.size()); + ASSERT_EQ(mh1.provider_to_time_domain_mappings_.size(), mh2.provider_to_time_domain_mappings_.size()); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_OnWriteNumberOfDomainsError_Aborts) { + ASSERT_EXIT( + { + // arrange + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + + EXPECT_CALL(*writer_mock, Write(testing::An())).WillOnce(Return(false)); + + // act + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing time domain mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_OnWriteDommainMappingError_Aborts) { + ASSERT_EXIT( + { + // arrange + AddMappings(); + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + + InSequence seq; + EXPECT_CALL(*writer_mock, Write(testing::An())).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::An())).WillOnce(Return(false)); + + // act + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing time domain mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_OnTooManyConsumerMappings_Aborts) { + auto fn = [] { + std::array consumer_names{}; + std::array consumer_names_views{}; + for (std::uint32_t i = 0; i < consumer_names.size(); ++i) { + consumer_names[i] = std::to_string(i).c_str(); + consumer_names_views[i] = consumer_names[i]; + + mh_->consumer_to_time_domain_mappings_.insert({consumer_names_views[i], {1u, kTimeDomainNames[0u]}}); + } + + // act + mh_->CommitMappingsToSharedMemory(); + }; + + ASSERT_EXIT( + { // arrange + AddMappings(); + // act + fn(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Number of consumer mappings has exceeded the maximum permissible value"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, CommitMappingsToSharedMemory_Succeeds) { + // act + mappings_handler_.CommitMappingsToSharedMemory(); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_WithTooManyTimeDomains_Aborts) { + ASSERT_EXIT( + { + std::vector domain_names; + std::string_view base_name{"MyDomain"}; + + // create domains + for (std::size_t i = 0; i <= TsyncIdMappingsHandler::kMaxNumberOfTimeDomains; ++i) { + domain_names.push_back(std::string(base_name.data()) + std::to_string(i).c_str()); + mh_->time_domains_.insert(std::make_pair(i + 1, std::string_view((--domain_names.end())->c_str()))); + } + + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Number of time domains has exceeded"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_WithDomainWriteError_Aborts) { + ASSERT_EXIT( + { + AddMappings(); + + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + + EXPECT_CALL(*writer_mock, GetState()).WillOnce(Return(ITimeBaseAccessor::State::Open)); + EXPECT_CALL(*writer_mock, + Write(testing::Matcher(static_cast(mh_->time_domains_.size())))) + .WillOnce(Return(true)); + // 1 is the id of the first time domain + EXPECT_CALL(*writer_mock, Write(testing::Matcher(mh_->time_domains_.begin()->first))) + .WillOnce(Return(false)); + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing time domain mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, + CommitMappingsToSharedMemory_WithConsumerMappingStartMagicWriteError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + + EXPECT_CALL(*writer_mock, GetState()).WillOnce(Return(ITimeBaseAccessor::State::Open)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(0xabcddcbaabcddcba))) + .WillOnce(Return(false)); + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing consumer mappings header"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_WithConsumerMappingWriteError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + AddMappings(); + mh_->AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0]); + + EXPECT_CALL(*writer_mock, GetState()).WillOnce(Return(ITimeBaseAccessor::State::Open)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kTimeDomainNames[0]))).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kTimeDomainNames[1]))).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kTimeDomainNames[2]))).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kTimeDomainNames[3]))).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(std::string_view("just_for_coverage")))) + .WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kTimeConsumerNames[0]))) + .WillOnce(Return(false)); + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing consumer mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, + CommitMappingsToSharedMemory_WithProviderMappingStartMagicWriteError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + + EXPECT_CALL(*writer_mock, GetState()).WillOnce(Return(ITimeBaseAccessor::State::Open)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kConsumerMagic))).WillOnce(Return(true)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kProviderMagic))).WillOnce(Return(false)); + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing provider mappings header"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, CommitMappingsToSharedMemory_WithProviderMappingWriteError_Aborts) { + ASSERT_EXIT( + { + AddMappings(); + + SharedMemTimeBaseWriterMock* writer_mock = static_cast(mh_->writer_.get()); + + EXPECT_CALL(*writer_mock, GetState()).WillOnce(Return(ITimeBaseAccessor::State::Open)); + EXPECT_CALL(*writer_mock, Write(testing::Matcher(kTimeProviderNames[0]))) + .WillOnce(Return(false)); + mh_->CommitMappingsToSharedMemory(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error writing provider mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, DoSharedMemoryRead_WithProviderMagicReadError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + + std::uint32_t num_domains = 1u; + std::uint32_t domain_id = 1u; + std::uint32_t num_consumer_mappings = 0u; + std::uint64_t corrupt_provider_magic = 0u; + + // read of configured time domains (number id, name) + testing::InSequence seq; + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(domain_id), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeDomainNames[0]), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kConsumerMagic), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(num_consumer_mappings), ::testing::Return(true))); + // read of provider magic fails + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(corrupt_provider_magic), ::testing::Return(true))); + mh_->DoSharedMemoryRead(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error reading start of provider mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, DoSharedMemoryRead_OnAddProviderToDomainMappingError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + + std::uint32_t num_domains = 1u; + std::uint32_t domain_id = 1u; + std::uint32_t invalid_domain_id = 42u; + std::uint32_t num_consumer_mappings = 0u; + std::uint32_t num_provider_mappings = 1u; + + // read of configured time domains (number id, name) + testing::InSequence seq; + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(domain_id), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeDomainNames[0]), ::testing::Return(true))); + // read of consumer magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kConsumerMagic), ::testing::Return(true))); + // read of number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(num_consumer_mappings), ::testing::Return(true))); + // read of provider magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kProviderMagic), ::testing::Return(true))); + // read of number of provider mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(num_provider_mappings), ::testing::Return(true))); + // add provider mapping to non-existing domain + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(kTimeProviderNames[0]), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(invalid_domain_id), ::testing::Return(true))); + + mh_->DoSharedMemoryRead(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error adding provider"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, DoSharedMemoryRead_Succeeds) { + TsyncIdMappingsHandler mh; + mh.reader_ = TimeBaseReaderFactory::Create(kIdMappingsShmemFileName, kIdMappingsShmemSize); + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh.reader_.get()); + + std::uint32_t num_domains = 1u; + std::uint32_t domain_id = 1u; + std::uint32_t num_consumer_mappings = 1u; + std::uint32_t num_provider_mappings = 1u; + + // read of configured time domains (number id, name) + testing::InSequence seq; + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(domain_id), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeDomainNames[0]), ::testing::Return(true))); + // read of consumer magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kConsumerMagic), ::testing::Return(true))); + // read of number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_consumer_mappings), ::testing::Return(true))); + // read of consumer mapping + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeConsumerNames[0]), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(domain_id), ::testing::Return(true))); + // read of provider magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kProviderMagic), ::testing::Return(true))); + // read of number of provider mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_provider_mappings), ::testing::Return(true))); + // read of provider mapping + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeProviderNames[0]), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(domain_id), ::testing::Return(true))); + + mh.DoSharedMemoryRead(); + SUCCEED(); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, DoSharedMemoryRead_WithConsumerMagicReadError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + std::uint32_t num_domains = 1; + std::uint32_t domain_id = 1; + std::uint64_t corrupt_magic = 0; + + // read of configured time domains (number, id, name) + testing::InSequence seq; + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(domain_id), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeDomainNames[0]), ::testing::Return(true))); + // failure to read consumer magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(corrupt_magic), ::testing::Return(true))); + // number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + + mh_->DoSharedMemoryRead(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error reading start of consumer mappings"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, DoSharedMemoryRead_WithInvalidDomainIdReadError_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + + std::uint32_t num_domains = 1; + std::uint32_t num_consumer_mappings = 0; + std::uint32_t num_provider_mappings = 1; + std::uint32_t valid_domain_id = 5; + std::uint32_t invalid_domain_id = 77; + + testing::InSequence seq; + // number of domain mapping + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + // domain id + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(valid_domain_id), ::testing::Return(true))); + // domain name + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kTimeDomainNames[0]), ::testing::Return(true))); + // consumer mappings magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kConsumerMagic), ::testing::Return(true))); + // number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(num_consumer_mappings), ::testing::Return(true))); + // provider mappings magic + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(kProviderMagic), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(num_provider_mappings), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce( + ::testing::DoAll(::testing::SetArgReferee<0>(kTimeProviderNames[0]), ::testing::Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(invalid_domain_id), ::testing::Return(true))); + mh_->DoSharedMemoryRead(); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error adding provider"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainId_WithValidDomainName_Succeeds) { + // arranged through fixture + + // act + auto ret = mappings_handler_.GetDomainId(kTimeDomainNames[0]); + + // assert + ASSERT_EQ(ret, 1U); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainId_WithNonexistentDomainName_Fails) { + // arranged through test-fixture + + // act + auto ret = mappings_handler_.GetDomainId(invalid_time_base_name_); + + // assert + ASSERT_FALSE(ret); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetDomainId_OnNumMappingsIsZero_Aborts) { + ASSERT_EXIT( + { + // arrange + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + std::uint32_t num_domains = 0u; + + EXPECT_CALL(*reader_mock, Read(::testing::An())) + .WillOnce(DoAll(SetArgReferee<0>(num_domains), Return(true))); + + // act + auto ret = mh_->GetDomainId("XYZ"); + }, + ::testing::ExitedWithCode(EXIT_CODE), "No domain mappings found"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetDomainId_OnShMemReadError_Aborts) { + ASSERT_EXIT( + { + auto reader_mock = mh_->reader_.get(); + auto raw_reader_mock = static_cast(reader_mock); + EXPECT_CALL(*raw_reader_mock, Read(::testing::An())).WillOnce(::testing::Return(false)); + + // act + mh_->GetDomainId("XYZ"); + }, + ::testing::ExitedWithCode(EXIT_CODE), "No domain mappings found"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainName_WithValidDomainId_Succeeds) { + // arranged through fixture + + // act + auto ret = mappings_handler_.GetDomainName(1); + + // assert + ASSERT_EQ(ret, kTimeDomainNames[0]); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainName_WithInvalidTimeBaseId_Fails) { + // arranged through fixture + + // act + auto ret = mappings_handler_.GetDomainName(invalid_timebase_id_); + + // assert + ASSERT_FALSE(ret); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainName_WithNonexistentTimeBaseId_Fails) { + // arranged through fixture + + // act + auto ret = mappings_handler_.GetDomainName(nonexistent_timebase_id_); + + // assert + ASSERT_FALSE(ret); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, DoSharedMemoryRead_OnNumMappingsIsZero_Aborts) { + ASSERT_EXIT( + { + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + std::uint32_t num_domains = 0u; + + EXPECT_CALL(*reader_mock, Read(::testing::An())) + .WillOnce(DoAll(SetArgReferee<0>(num_domains), Return(true))); + // act + mh_->GetDomainName(1); + }, + ::testing::ExitedWithCode(EXIT_CODE), "No domain mappings found"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddDomainMapping_WithValidParams_Succeeds) { + // arrange + TsyncIdMappingsHandler mappings_handler; + + // act AddDomainMapping increments num_mappings_ by one + auto ret = mappings_handler.AddDomainMapping(1, kTimeDomainNames[0]); + + // assert + ASSERT_TRUE(ret); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddDomainMapping_WithInvalidId_Failed) { + // arrange + TsyncIdMappingsHandler mappings_handler; + + // act AddDomainMapping increments num_mappings_ by one + auto ret = mappings_handler.AddDomainMapping(100, kTimeDomainNames[0]); + + // assert + ASSERT_FALSE(ret); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddDomainMapping_WithDomaiHaveInvalidId_Failed) { + // arrange + TsyncIdMappingsHandler mappings_handler; + TsyncIdMappingsHandler::TimeDomainMapping domain_mapping; + domain_mapping.first = 100; + domain_mapping.second = "Invalid_domain"; + + // act AddDomainMapping increments num_mappings_ by one + auto ret = mappings_handler.AddDomainMapping(domain_mapping); + + // assert + ASSERT_FALSE(ret); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainName_WithReadFromSharedMemory_Succeeds) { + // arrange + TsyncIdMappingsHandler mh; + mh.reader_ = TimeBaseReaderFactory::Create(kIdMappingsShmemFileName, kIdMappingsShmemSize); + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh.reader_.get()); + + uint32_t domain_count_and_id = 1; + uint32_t mapping_count = 0; + InSequence seq; + EXPECT_CALL(*reader_mock, Open()).Times(1); + EXPECT_CALL(*reader_mock, lock()).Times(1); + // read of domain count, domain id & name + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[0]), Return(true))); + // read of consumer magic and number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kConsumerMagic), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(mapping_count), Return(true))); + // read of provider magic and number of provider mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kProviderMagic), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(mapping_count), Return(true))); + + EXPECT_CALL(*reader_mock, unlock()).Times(1); + + auto res = mh.GetDomainName(1); + + ASSERT_TRUE(res); + ASSERT_EQ(kTimeDomainNames[0], *res); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetDomainName_OnDomainMappingReadFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + + uint32_t count_and_id = 1; + EXPECT_CALL(*reader_mock, Open()).Times(1); + EXPECT_CALL(*reader_mock, lock()).Times(1); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillRepeatedly(::testing::DoAll(testing::SetArgReferee<0>(count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())).WillOnce(Return(false)); + + // act + mh_->GetDomainName(1); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error reading domain mapping"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetDomainName_OnConsumerMappingReadFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + + uint32_t domain_count_and_id = 1; + uint32_t consumer_count = 2; + InSequence seq; + EXPECT_CALL(*reader_mock, Open()).Times(1); + EXPECT_CALL(*reader_mock, lock()).Times(1); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[0]), Return(true))); + // read of consumer magic and number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kConsumerMagic), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(consumer_count), Return(true))); + // failure to read consumer mapping + EXPECT_CALL(*reader_mock, Read(testing::An())).WillOnce(Return(false)); + + // act + mh_->GetDomainName(1); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error reading consumer mapping"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetDomainName_OnProviderMappingReadFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + SharedMemTimeBaseReaderMock* reader_mock = static_cast(mh_->reader_.get()); + + uint32_t domain_count_and_id = 1; + uint32_t consumer_count = 1; + uint32_t provider_count = 1; + InSequence seq; + EXPECT_CALL(*reader_mock, Open()).Times(1); + EXPECT_CALL(*reader_mock, lock()).Times(1); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeDomainNames[0]), Return(true))); + // read of consumer magic and number of consumer mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kConsumerMagic), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(consumer_count), Return(true))); + // read of consumer mapping + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kTimeConsumerNames[0]), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(domain_count_and_id), Return(true))); + // read of provider magic and number of provider mappings + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(kProviderMagic), Return(true))); + EXPECT_CALL(*reader_mock, Read(testing::An())) + .WillOnce(::testing::DoAll(testing::SetArgReferee<0>(provider_count), Return(true))); + // failure to read provider mapping + EXPECT_CALL(*reader_mock, Read(testing::An())).WillOnce(Return(false)); + // act + mh_->GetDomainName(1); + }, + ::testing::ExitedWithCode(EXIT_CODE), "Error reading provider mapping"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, IterateTimeDomains_Succeeds) { + int32_t cnt = 0; + for (const auto& it : mappings_handler_) { + (void)it; + ++cnt; + } + + // subtract 1 for the "invalid" time domain + ASSERT_EQ(cnt - 1, kNumTimeDomains); +} + +TEST_F(TsyncIdMappingsHandlerFixture, IsEmpty_OnEmpty_Succeeds) { + TsyncIdMappingsHandler mh; + ASSERT_TRUE(mh.IsEmpty()); +} + +TEST_F(TsyncIdMappingsHandlerFixture, IterateConstTimeDomains_Succeeds) { + const auto& cmh = mappings_handler_; + + int32_t cnt = 0; + for (auto it = cmh.cbegin(); it != cmh.cend(); ++it) { + ++cnt; + } + + // subtract 1 for the "invalid" time domain + ASSERT_EQ(cnt - 1, kNumTimeDomains); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddConsumerToDomain_Succeeds) { + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(2U, kTimeConsumerNames[1])); + + // for coverage + mappings_handler_.DumpMappings(); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddConsumerToDomain_SameConsumerTwice_Fails) { + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); + ASSERT_FALSE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddConsumerToDomain_WithInvalidDomain_Fails) { + ASSERT_FALSE(mappings_handler_.AddConsumerToDomain("bla", kTimeConsumerNames[0])); + ASSERT_FALSE(mappings_handler_.AddConsumerToDomain(17U, kTimeConsumerNames[1])); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainNameForConsumer_Succeeds) { + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); + ASSERT_EQ(*(mappings_handler_.GetDomainNameForConsumer(kTimeConsumerNames[0])), kTimeDomainNames[0]); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainNameForConsumer_WithInvalidDomain_Fails) { + ASSERT_FALSE(mappings_handler_.GetDomainNameForConsumer("bla")); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainNameForConsumer_WithInvalidConsumer_Fails) { + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); + ASSERT_FALSE(mappings_handler_.GetDomainNameForConsumer("bla")); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainIdForConsumer_Succeeds) { + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); + ASSERT_EQ(*(mappings_handler_.GetDomainIdForConsumer(kTimeConsumerNames[0])), 1U); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainIdForConsumer_WithInvalidDomain_Fails) { + ASSERT_FALSE(mappings_handler_.GetDomainIdForConsumer("bla")); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainIdForConsumer_WithInvalidConsumer_Fails) { + ASSERT_TRUE(mappings_handler_.AddConsumerToDomain(kTimeDomainNames[0], kTimeConsumerNames[0])); + ASSERT_FALSE(mappings_handler_.GetDomainIdForConsumer("bla")); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddProviderToDomain_Succeeds) { + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0])); + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(2U, kTimeProviderNames[1])); + + // for coverage + mappings_handler_.DumpMappings(); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddProviderToDomain_MoreThanOneProvider_Fails) { + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0])); + ASSERT_FALSE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[1])); + ASSERT_FALSE(mappings_handler_.AddProviderToDomain(1, kTimeProviderNames[1])); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddProviderToDomain_WithInvalidDomain_Fails) { + ASSERT_FALSE(mappings_handler_.AddProviderToDomain("bla", kTimeProviderNames[0])); + ASSERT_FALSE(mappings_handler_.AddProviderToDomain(17U, kTimeProviderNames[1])); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainNameForProvider_Succeeds) { + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0])); + ASSERT_EQ(*(mappings_handler_.GetDomainNameForProvider(kTimeProviderNames[0])), kTimeDomainNames[0]); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainNameForProvider_WithInvalidProvider_Fails) { + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0])); + ASSERT_FALSE(mappings_handler_.GetDomainNameForProvider("bla")); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainIdForProvider_Succeeds) { + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0])); + ASSERT_EQ(*(mappings_handler_.GetDomainIdForProvider(kTimeProviderNames[0])), 1U); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetDomainIdForProvider_WithInvalidProvider_Fails) { + ASSERT_TRUE(mappings_handler_.AddProviderToDomain(kTimeDomainNames[0], kTimeProviderNames[0])); + ASSERT_FALSE(mappings_handler_.GetDomainIdForProvider("bla")); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetDomainIdForConsumer_OnZeroTimeDomains_Aborts) { + ASSERT_EXIT( + { + // arrange + uint32_t num_domains = 0; + + // arrange + auto reader_mock = mh_->reader_.get(); + auto raw_reader_mock = static_cast(reader_mock); + EXPECT_CALL(*raw_reader_mock, Read(::testing::An())) + .WillOnce(::testing::DoAll(::testing::SetArgReferee<0>(num_domains), ::testing::Return(true))); + + // act + mh_->GetDomainIdForConsumer(kTimeConsumerNames[0]); + }, + ::testing::ExitedWithCode(EXIT_CODE), "No domain mappings found"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetProviderForDomain_OnReadNumberOfTimeDomainsFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + auto reader_mock = mh_->reader_.get(); + auto raw_reader_mock = static_cast(reader_mock); + EXPECT_CALL(*raw_reader_mock, Read(::testing::An())).WillOnce(::testing::Return(false)); + + // act + auto r = mh_->GetProviderForDomain(kTimeDomainNames[0]); + }, + ::testing::ExitedWithCode(EXIT_CODE), "No domain mappings found"); +} + +TEST_F(TsyncIdMappingsHandlerDeathFixture, GetProviderForDomainId_OnDoSharedMemoryReadFailure_Aborts) { + ASSERT_EXIT( + { + // arrange + auto reader_mock = mh_->reader_.get(); + auto raw_reader_mock = static_cast(reader_mock); + EXPECT_CALL(*raw_reader_mock, Read(::testing::An())).WillOnce(::testing::Return(false)); + + // act + auto r = mh_->GetProviderForDomain(1); + }, + ::testing::ExitedWithCode(EXIT_CODE), "No domain mappings found"); +} + +TEST_F(TsyncIdMappingsHandlerFixture, GetProviderForDomainId_WithInvalidDomainId_Fails) { + // act + auto r = mappings_handler_.GetProviderForDomain(invalid_timebase_id_); + + // assert + ASSERT_FALSE(r); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddConsumerToDomainMapping_OnEmpty_Fails) { + // arrange + TsyncIdMappingsHandler mh; + + ASSERT_FALSE(mh.reader_); + ASSERT_FALSE(mh.writer_); + ASSERT_TRUE(mh.time_domains_.empty()); + ASSERT_TRUE(mh.provider_to_time_domain_mappings_.empty()); + ASSERT_TRUE(mh.consumer_to_time_domain_mappings_.empty()); + // act + bool res = mh.AddConsumerToDomainMapping(1, std::string_view("domain_1")); + // assert + EXPECT_FALSE(res); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddProviderToDomainMapping_OnEmpty_Fails) { + // arrange + TsyncIdMappingsHandler mh; + + ASSERT_FALSE(mh.reader_); + ASSERT_FALSE(mh.writer_); + ASSERT_TRUE(mh.time_domains_.empty()); + ASSERT_TRUE(mh.provider_to_time_domain_mappings_.empty()); + ASSERT_TRUE(mh.consumer_to_time_domain_mappings_.empty()); + // act + bool res = mh.AddProviderToDomainMapping(1, std::string_view("domain_1")); + // assert + EXPECT_FALSE(res); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddProviderToDomainMapping_Succeeds) { + // arrange + // act + bool res = mappings_handler_.AddProviderToDomainMapping(1, std::string_view("test_provider")); + // assert + EXPECT_TRUE(res); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddConsumerToDomainMapping_OnInvalidDomain_Fails) { + ASSERT_FALSE(mappings_handler_.AddConsumerToDomainMapping(17U, kTimeConsumerNames[1])); +} + +TEST_F(TsyncIdMappingsHandlerFixture, AddProviderToDomainMapping_OnInvalidDomain_Fails) { + ASSERT_FALSE(mappings_handler_.AddProviderToDomainMapping(17U, kTimeProviderNames[1])); +} + +} // namespace lib_tsyncidmappingshandler_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/TsyncNamedSemaphore_UT/BUILD b/src/tsync-utility-lib/test/TsyncNamedSemaphore_UT/BUILD new file mode 100644 index 0000000..d883b2b --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncNamedSemaphore_UT/BUILD @@ -0,0 +1,16 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TsyncNamedSemaphore_UT", + srcs = [ + "TsyncNamedSemaphore_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/tsync-utility-lib:utility_tsync_named_semaphore", + "//src/common", + "@score_baselibs//score/language/futurecpp", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/TsyncNamedSemaphore_UT/TsyncNamedSemaphore_UT.cpp b/src/tsync-utility-lib/test/TsyncNamedSemaphore_UT/TsyncNamedSemaphore_UT.cpp new file mode 100644 index 0000000..3743b0b --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncNamedSemaphore_UT/TsyncNamedSemaphore_UT.cpp @@ -0,0 +1,295 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include +#include + +#include +#include + +#define private public +#include "score/time/utility/TsyncNamedSemaphore.h" +#undef private +#include "SysCallsNamedSemMock.h" + +using namespace score::time; + +#include + +class TSyncNamedSemaphoreTestFixture : public ::testing::Test { +public: + static const int32_t EXIT_CODE; + static const char* TEST_LOCK_NAME; + static const char* TEST_LOCK_NAME_2; + +protected: + void SetUp() override { + named_semaphore_mock = std::make_unique<::testing::NiceMock>(); + + // Setup default expectations + // unsignaled open + ON_CALL(*named_semaphore_mock, OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, 0)) + .WillByDefault(::testing::DoAll([this]() { this->sem_val_ = 0; }, ::testing::Return(&dummy_sem_))); + ON_CALL(*named_semaphore_mock, OsSemOpen(::testing::StrEq(TEST_LOCK_NAME_2), ::testing::_, ::testing::_, 0)) + .WillByDefault(::testing::DoAll([this]() { this->sem_val_2_ = 0; }, ::testing::Return(&dummy_sem_2_))); + // signaled open + ON_CALL(*named_semaphore_mock, OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, 1)) + .WillByDefault(::testing::DoAll([this]() { this->sem_val_ = 1; }, ::testing::Return(&dummy_sem_))); + ON_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*named_semaphore_mock, OsSemUnlink(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*named_semaphore_mock, OsSemWait(::testing::_)) + .WillByDefault(::testing::DoAll( + [this]() { + if (this->sem_val_ > 0) { + --this->sem_val_; + } + }, + ::testing::Return(0))); + ON_CALL(*named_semaphore_mock, OsSemPost(::testing::_)) + .WillByDefault(::testing::DoAll([this]() { ++this->sem_val_; }, ::testing::Return(0))); + ON_CALL(*named_semaphore_mock, OsSemTryWait(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*named_semaphore_mock, OsSemGetValue(::testing::_, ::testing::_)) + .WillByDefault(::testing::DoAll(testing::SetArgPointee<1>(::testing::ByRef(sem_val_)), testing::Return(0))); + + ::testing::Mock::AllowLeak(named_semaphore_mock.get()); + std::signal(SIGABRT, AbortHandler); + } + + void TearDown() override { + // As we use here singleton mock object, clear expectations after each test + named_semaphore_mock.reset(nullptr); + std::signal(SIGABRT, SIG_DFL); + } + + static void AbortHandler(int /*signal*/) noexcept { + // the mock has to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + named_semaphore_mock.reset(nullptr); + std::exit(TSyncNamedSemaphoreTestFixture::EXIT_CODE); + } + + sem_t dummy_sem_; + sem_t dummy_sem_2_; + unsigned int sem_val_; + unsigned int sem_val_2_; +}; + +const int32_t TSyncNamedSemaphoreTestFixture::EXIT_CODE = 42; +const char* TSyncNamedSemaphoreTestFixture::TEST_LOCK_NAME = "/TsyncNamedSemaphore_test"; +const char* TSyncNamedSemaphoreTestFixture::TEST_LOCK_NAME_2 = "/TsyncNamedSemaphore_test_2"; + +namespace testing { +namespace lib_tsyncnamedsemaphore_ut { + +TEST_F(TSyncNamedSemaphoreTestFixture, MoveAssignmentOperator_Succeeds) { + TsyncNamedSemaphore source(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + TsyncNamedSemaphore dest(TEST_LOCK_NAME_2, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + EXPECT_STREQ(dest.name_.c_str(), TEST_LOCK_NAME_2); + EXPECT_EQ(dest.is_owner_, true); + dest = std::move(source); + EXPECT_STREQ(dest.name_.c_str(), TEST_LOCK_NAME); + EXPECT_EQ(dest.is_owner_, true); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, TryLock_InSignaledState_Succeeds) { + // arrange + EXPECT_CALL(*named_semaphore_mock, + OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, ::testing::Eq(1))) + .Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemTryWait(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemUnlink(::testing::StrEq(TEST_LOCK_NAME))).Times(1); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Signaled, true); + + // act + auto result = lock.try_lock(); + + // assert + ASSERT_TRUE(result); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, TryLock_InUnsignaledState_Fails) { + // arrange + EXPECT_CALL(*named_semaphore_mock, + OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, ::testing::Eq(0))) + .Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemTryWait(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemUnlink(::testing::StrCaseEq(TEST_LOCK_NAME))).Times(1); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + + // act + auto result = lock.try_lock(); + + // assert + EXPECT_FALSE(result); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, Ctor_OnSemOpenFailure_Aborts) { + // arrange + ASSERT_EXIT( + { + EXPECT_CALL(*named_semaphore_mock, OsSemOpen(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .Times(1) + .WillOnce(::testing::Return(SEM_FAILED)); + + // act + TsyncNamedSemaphore sem(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Signaled); + }, + ::testing::ExitedWithCode(TSyncNamedSemaphoreTestFixture::EXIT_CODE), ".*failed to create semaphore.*"); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, GetValue_OnError_ReturnsMinusOne) { + // arrange + EXPECT_CALL(*named_semaphore_mock, + OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, ::testing::Eq(1))) + .Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemGetValue(::testing::_, ::testing::_)) + .Times(1) + .WillOnce(::testing::Return(-1)); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemUnlink(::testing::StrEq(TEST_LOCK_NAME))).Times(1); + + TsyncNamedSemaphore sem(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Signaled, true); + + // act + auto result = sem.get_value(); + + // assert + ASSERT_EQ(result, -1); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, GetValue_WithVariousSemaphoreStates_ReturnsCorrectValues) { + // arrange + EXPECT_CALL(*named_semaphore_mock, + OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, ::testing::Eq(0))) + .Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemWait(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemPost(::testing::_)).Times(3); + EXPECT_CALL(*named_semaphore_mock, OsSemGetValue(::testing::_, ::testing::_)).Times(5); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemUnlink(::testing::StrEq(TEST_LOCK_NAME))).Times(1); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + + // act and assert (various steps) + int v = lock.get_value(); + ASSERT_EQ(0, v); + lock.unlock(); + v = lock.get_value(); + ASSERT_EQ(1, v); + lock.unlock(); + ASSERT_EQ(2, lock.get_value()); + lock.unlock(); + ASSERT_EQ(3, lock.get_value()); + lock.lock(); + ASSERT_EQ(2, lock.get_value()); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, TryLock_WithUnsignaledOwnedSemapore_Fails) { + // arrange + EXPECT_CALL(*named_semaphore_mock, + OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, ::testing::Eq(0))) + .Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemTryWait(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemUnlink(::testing::StrEq(TEST_LOCK_NAME))).Times(1); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + + // act + auto result = lock.try_lock(); + + // assert + ASSERT_FALSE(result); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, TryLock_WithUnsignaledSemapore_Fails) { + // arrange + EXPECT_CALL(*named_semaphore_mock, + OsSemOpen(::testing::StrEq(TEST_LOCK_NAME), ::testing::_, ::testing::_, ::testing::Eq(0))) + .Times(1); + EXPECT_CALL(*named_semaphore_mock, OsSemTryWait(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, false); + + // act + auto result = lock.try_lock(); + + // assert + ASSERT_FALSE(result); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, Dtor_OnSemCloseError_Aborts) { + // arrange + ASSERT_EXIT( + { + std::unique_ptr sem = + std::make_unique(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + EXPECT_CALL(*named_semaphore_mock, OsSemClose(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + sem.reset(); + }, + ::testing::ExitedWithCode(TSyncNamedSemaphoreTestFixture::EXIT_CODE), ".*sem_close failed for semaphore.*"); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, Dtor_OnSemUnlinkError_Aborts) { + // arrange + ASSERT_EXIT( + { + std::unique_ptr sem = + std::make_unique(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled, true); + EXPECT_CALL(*named_semaphore_mock, OsSemUnlink(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + sem.reset(); + }, + ::testing::ExitedWithCode(TSyncNamedSemaphoreTestFixture::EXIT_CODE), ".*sem_unlink failed for semaphore.*"); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, Lock_OnError_Aborts) { + // arrange + ASSERT_EXIT( + { + EXPECT_CALL(*named_semaphore_mock, OsSemWait(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled); + + // act + lock.lock(); + }, + ::testing::ExitedWithCode(TSyncNamedSemaphoreTestFixture::EXIT_CODE), ".*sem_wait failed for semaphore.*"); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, Lock_OnInterrupt_Success) { + // arrange + EXPECT_CALL(*named_semaphore_mock, OsSemWait(::testing::_)) + .WillOnce([]() { + errno = EINTR; + return -1; + }) + .WillOnce(Return(0)); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled); + + // act + lock.lock(); +} + +TEST_F(TSyncNamedSemaphoreTestFixture, Unlock_OnError_Aborts) { + // arrange + ASSERT_EXIT( + { + EXPECT_CALL(*named_semaphore_mock, OsSemPost(::testing::_)).Times(1).WillOnce(::testing::Return(-1)); + + TsyncNamedSemaphore lock(TEST_LOCK_NAME, TsyncNamedSemaphore::OpenMode::Unsignaled); + + // act + lock.unlock(); + }, + ::testing::ExitedWithCode(TSyncNamedSemaphoreTestFixture::EXIT_CODE), ".*sem_post failed for semaphore.*"); +} + +} // namespace lib_tsyncnamedsemaphore_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/TsyncReadWriteLock_UT/BUILD b/src/tsync-utility-lib/test/TsyncReadWriteLock_UT/BUILD new file mode 100644 index 0000000..0924770 --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncReadWriteLock_UT/BUILD @@ -0,0 +1,14 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TsyncReadWriteLock_UT", + srcs = [ + "TsyncReadWriteLock_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/common", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/TsyncReadWriteLock_UT/TsyncReadWriteLock_UT.cpp b/src/tsync-utility-lib/test/TsyncReadWriteLock_UT/TsyncReadWriteLock_UT.cpp new file mode 100644 index 0000000..a50eb1d --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncReadWriteLock_UT/TsyncReadWriteLock_UT.cpp @@ -0,0 +1,193 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include + +#define private public +#include "TsyncReadWriteLock.h" +#undef private +#include "score/span.hpp" +#include "SysCallsReadWriteLockMock.h" + +using namespace score::time; +extern std::unique_ptr> score::time::rw_lock_mock; + +class TSyncReadWriteLockTestFixture : public ::testing::Test { +public: + static const int32_t EXIT_CODE; + +protected: + void SetUp() override { + rw_lock_mock = std::make_unique>(); + + // Setup default expectations for success cases + ON_CALL(*rw_lock_mock, OsRwLockInitAttr(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockDestroyAttr(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockAttrSetPShared(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockInit(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockTryReadLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockReadLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockTryWriteLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockWriteLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockUnlock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*rw_lock_mock, OsRwLockDestroyLock(::testing::_)).WillByDefault(::testing::Return(0)); + + // install abort handler for our death tests + std::signal(SIGABRT, AbortHandler); + // As we use here singleton mock object, clear expectations after each test + ::testing::Mock::AllowLeak(rw_lock_mock.get()); + } + + void TearDown() override { + rw_lock_mock.reset(); + std::signal(SIGABRT, SIG_DFL); + } + + static void AbortHandler(int /*signal*/) noexcept { + // the mock has to be reset here, otherwise the expectations for our death tests + // will never be met/evaluated. + rw_lock_mock.reset(); + std::exit(EXIT_CODE); + } +}; + +const int32_t TSyncReadWriteLockTestFixture::EXIT_CODE = 42; + +namespace testing { +namespace lib_tsyncreadwritelock_ut { + +TEST_F(TSyncReadWriteLockTestFixture, MoveConstruction_Success) { + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Read, true); + auto ownership = lock.is_owner_; + auto ptr = lock.rw_lock_; + TsyncReadWriteLock lock2(std::move(lock)); + + ASSERT_EQ(ownership, lock2.is_owner_); + ASSERT_EQ(ptr, lock2.rw_lock_); + ASSERT_EQ(lock.rw_lock_, nullptr); +} + +TEST_F(TSyncReadWriteLockTestFixture, MoveAssignment_Success) { + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Read, true); + auto ownership = lock.is_owner_; + auto ptr = lock.rw_lock_; + TsyncReadWriteLock lock2; + lock2 = std::move(lock); + + ASSERT_EQ(ownership, lock2.is_owner_); + ASSERT_EQ(ptr, lock2.rw_lock_); + ASSERT_EQ(lock.rw_lock_, nullptr); +} + +TEST_F(TSyncReadWriteLockTestFixture, Open_OsRwLockInitAttrFail_Exit) { + // Assert + ASSERT_EXIT( + { + // Arange + EXPECT_CALL(*rw_lock_mock, OsRwLockInitAttr(::testing::_)).WillOnce(::testing::Return(-1)); + + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Read, true); + }, + ::testing::ExitedWithCode(TSyncReadWriteLockTestFixture::EXIT_CODE), "pthreadrw_lock_attr_init failed"); +} + +TEST_F(TSyncReadWriteLockTestFixture, Open_OsRwLockAttrSetPSharedFail_Exit) { + // Assert + ASSERT_EXIT( + { + // Arange + EXPECT_CALL(*rw_lock_mock, OsRwLockAttrSetPShared(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(-1)); + + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Read, true); + }, + ::testing::ExitedWithCode(TSyncReadWriteLockTestFixture::EXIT_CODE), "pthreadrw_lock_attr_setpshared failed"); +} + +TEST_F(TSyncReadWriteLockTestFixture, Open_OsRwLockInitFail_Exit) { + // Assert + ASSERT_EXIT( + { + // Arange + EXPECT_CALL(*rw_lock_mock, OsRwLockInit(::testing::_, ::testing::_)).WillOnce(::testing::Return(-1)); + + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Write, true); + }, + ::testing::ExitedWithCode(TSyncReadWriteLockTestFixture::EXIT_CODE), "pthreadrw_lock_init failed"); +} + +TEST_F(TSyncReadWriteLockTestFixture, Open_ForNonOwnedReadLock_Success) { + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Read, false); + // Assert + SUCCEED(); +} + +TEST_F(TSyncReadWriteLockTestFixture, try_lock_OsRwLockTryWriteLockSuccess_Success) { + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Write, true); + bool res = lock.try_lock(); + lock.unlock(); + // Assert + ASSERT_TRUE(res); +} + +TEST_F(TSyncReadWriteLockTestFixture, lock_OsRwLockWriteLockFail_Exit) { + // Assert + ASSERT_EXIT( + { + // Arange + EXPECT_CALL(*rw_lock_mock, OsRwLockWriteLock(::testing::_)).WillOnce(::testing::Return(-1)); + + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Write, true); + + lock.lock(); + }, + ::testing::ExitedWithCode(TSyncReadWriteLockTestFixture::EXIT_CODE), "TsyncReadWriteLock::lock\\(\\) failed"); +} + +TEST_F(TSyncReadWriteLockTestFixture, unlock_OsRwLockUnlockFail_Exit) { + // Assert + ASSERT_EXIT( + { + // Arange + EXPECT_CALL(*rw_lock_mock, OsRwLockUnlock(::testing::_)).WillOnce(::testing::Return(-1)); + + // Act + pthread_rwlock_t attr; + TsyncReadWriteLock lock; + lock.Open(&attr, TsyncReadWriteLock::LockMode::Write, true); + + lock.lock(); + lock.unlock(); + }, + ::testing::ExitedWithCode(TSyncReadWriteLockTestFixture::EXIT_CODE), "TsyncReadWriteLock::unlock\\(\\) failed"); +} + +} // namespace lib_tsyncreadwritelock_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/TsyncSharedUtils_UT/BUILD b/src/tsync-utility-lib/test/TsyncSharedUtils_UT/BUILD new file mode 100644 index 0000000..798e139 --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncSharedUtils_UT/BUILD @@ -0,0 +1,18 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") + +cc_test( + name = "TsyncSharedUtils_UT", + srcs = [ + "TsyncSharedUtils_UT.cpp", + ], + deps = [ + "//src/tsync-utility-lib/test/mocks:utility_mocks", + "//src/tsync-utility-lib/test/matchers:test_matchers", + "//src/tsync-utility-lib:tsync_utility_if", + "//src/tsync-utility-lib:utility_tsync_shared_utils", + "//src/tsync-utility-lib:utility_tsync_named_semaphore", + "//src/common", + "@score_baselibs//score/language/futurecpp", + "@googletest//:gtest_main", + ] +) diff --git a/src/tsync-utility-lib/test/TsyncSharedUtils_UT/TsyncSharedUtils_UT.cpp b/src/tsync-utility-lib/test/TsyncSharedUtils_UT/TsyncSharedUtils_UT.cpp new file mode 100644 index 0000000..0d3b467 --- /dev/null +++ b/src/tsync-utility-lib/test/TsyncSharedUtils_UT/TsyncSharedUtils_UT.cpp @@ -0,0 +1,93 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include + +#include "SysCallsMiscMock.h" +#include "SysCallsNamedSemMock.h" + +#define private public +#include "score/time/utility/TsyncSharedUtils.h" +#undef private + +namespace score { +namespace time { +std::unique_ptr<::testing::NiceMock> misc_mock; +std::unique_ptr<::testing::NiceMock> named_semaphore_mock; +} // namespace time +} // namespace score + +using namespace score::time; + +using testing::_; +using testing::Invoke; + +class TsyncSharedUtilsTestFixture : public ::testing::Test { +public: + void SetUp() override { + tsync_util_ = std::make_unique(); + misc_mock = std::make_unique<::testing::NiceMock>(); + named_semaphore_mock = std::make_unique<::testing::NiceMock>(); + } + + void TearDown() override { + tsync_util_.reset(); + misc_mock.reset(); + named_semaphore_mock.reset(); + } + +protected: + std::unique_ptr tsync_util_; + sem_t dummy_semaphore; +}; + +namespace testing { +namespace lib_tsyncsharedutils_ut { + +TEST_F(TsyncSharedUtilsTestFixture, GetTransmissionSemaphoreName_ReturnValue_Succeed) { + std::uint16_t time_domain_id = 1; + std::string res = tsync_util_->GetTransmissionSemaphoreName(time_domain_id); + EXPECT_EQ(std::string("time_domain_1"), res); +} + +TEST_F(TsyncSharedUtilsTestFixture, CreateTransmissionSemaphore_ReturnValidSemaphore_Succeed) { + std::uint16_t time_domain_id = 1; + std::string domain_name = tsync_util_->GetTransmissionSemaphoreName(time_domain_id); + bool as_owner = false; + EXPECT_CALL(*named_semaphore_mock, OsSemOpen(::testing::StrEq("/time_domain_1"), _, _, _)) + .WillOnce(Return(&dummy_semaphore)); + + tsync_util_->CreateTransmissionSemaphore(1, as_owner); +} + +TEST_F(TsyncSharedUtilsTestFixture, GetCurrentVirtualLocalTime_OsClockGetTime_Failed) { + EXPECT_CALL(*misc_mock, OsClockGetTime(CLOCK_MONOTONIC, _)).WillOnce(Return(-1)); + + auto res = tsync_util_->GetCurrentVirtualLocalTime(); + + EXPECT_EQ(std::nullopt, res); +} + +TEST_F(TsyncSharedUtilsTestFixture, GetCurrentVirtualLocalTime_OsClockGetTime_Succeed) { + EXPECT_CALL(*misc_mock, OsClockGetTime(CLOCK_MONOTONIC, _)).WillOnce(Return(0)); + + auto res = tsync_util_->GetCurrentVirtualLocalTime(); + + EXPECT_NE(std::nullopt, res); +} + +TEST_F(TsyncSharedUtilsTestFixture, GetCurrentVirtualLocalTime_OsClockGetTime_Succeed_Overflow) { + timespec ts; + ts.tv_nsec = 999999999; + ts.tv_sec = std::numeric_limits::max(); // almost Overflowing sec + + EXPECT_CALL(*misc_mock, OsClockGetTime(_, _)).WillOnce(DoAll(SetArgPointee<1>(ts), Return(0))); + + auto res = tsync_util_->GetCurrentVirtualLocalTime(); + + EXPECT_NE(std::nullopt, res); +} + +} // namespace lib_tsyncsharedutils_ut +} // namespace testing diff --git a/src/tsync-utility-lib/test/matchers/BUILD b/src/tsync-utility-lib/test/matchers/BUILD new file mode 100644 index 0000000..b4f9b91 --- /dev/null +++ b/src/tsync-utility-lib/test/matchers/BUILD @@ -0,0 +1,15 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +# load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_library( + name = "test_matchers", + hdrs = [ + "matcher_operators.h", + ], + strip_include_prefix = ".", + deps = [ + "//src/tsync-utility-lib:tsync_utility_if", + "@score_baselibs//score/language/futurecpp", + ], + visibility = ["//visibility:public"] +) diff --git a/src/tsync-utility-lib/test/matchers/matcher_operators.h b/src/tsync-utility-lib/test/matchers/matcher_operators.h new file mode 100644 index 0000000..bd7c5cd --- /dev/null +++ b/src/tsync-utility-lib/test/matchers/matcher_operators.h @@ -0,0 +1,92 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_MATCHER_OPERATORS_H_ +#define SCORE_TIME_MATCHER_OPERATORS_H_ + +#include "score/span.hpp" + +#include "score/time/utility/TsyncConfigTypes.h" +#include "score/time/utility/ITimeBaseAccessor.h" + +namespace score { +namespace time { + +inline bool operator==(const TsyncConsumerConfig& lhs, const TsyncConsumerConfig& rhs) { + return ((lhs.name == rhs.name) && + (lhs.time_slave_config.is_valid == rhs.time_slave_config.is_valid) && (lhs.time_slave_config.follow_up_timeout_value == rhs.time_slave_config.follow_up_timeout_value) && + (lhs.time_slave_config.time_leap_future_threshold == rhs.time_slave_config.time_leap_future_threshold) && + (lhs.time_slave_config.time_leap_healing_counter == rhs.time_slave_config.time_leap_healing_counter) && + (lhs.time_slave_config.time_leap_past_threshold == rhs.time_slave_config.time_leap_past_threshold)); +} + +inline bool operator!=(const TsyncConsumerConfig& lhs, const TsyncConsumerConfig& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(const TsyncProviderConfig& lhs, const TsyncProviderConfig& rhs) { + return ((lhs.name == rhs.name) && + (lhs.time_master_config.is_valid == rhs.time_master_config.is_valid) && + (lhs.time_master_config.immediate_resume_time == rhs.time_master_config.immediate_resume_time) && + (lhs.time_master_config.is_system_wide_global_time_master == rhs.time_master_config.is_system_wide_global_time_master) && + (lhs.time_master_config.sync_period == rhs.time_master_config.sync_period) && + (lhs.time_sync_correction_config.is_valid == rhs.time_sync_correction_config.is_valid) && + (lhs.time_sync_correction_config.allow_provider_rate_correction == rhs.time_sync_correction_config.allow_provider_rate_correction) && + (lhs.time_sync_correction_config.num_rate_corrections_per_measurement_duration == rhs.time_sync_correction_config.num_rate_corrections_per_measurement_duration) && + (lhs.time_sync_correction_config.offset_correction_adaption_interval == rhs.time_sync_correction_config.offset_correction_adaption_interval) && + (lhs.time_sync_correction_config.offset_correction_jump_threshold == rhs.time_sync_correction_config.offset_correction_jump_threshold) && + (lhs.time_sync_correction_config.rate_deviation_measurement_duration == rhs.time_sync_correction_config.rate_deviation_measurement_duration)); +} + +inline bool operator!=(const TsyncProviderConfig& lhs, const TsyncProviderConfig& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(const TsyncEthTimeDomain& lhs, const TsyncEthTimeDomain& rhs) { + return ( lhs.is_valid == rhs.is_valid && + lhs.message_format == rhs.message_format && + lhs.num_fup_data_id_entries == rhs.num_fup_data_id_entries && + lhs.vlan_priority == rhs.vlan_priority && + memcmp(&lhs.dest_mac_address, &rhs.dest_mac_address, sizeof(lhs.dest_mac_address)) == 0 && + memcmp(&lhs.fup_data_id_list, &rhs.fup_data_id_list, sizeof(lhs.fup_data_id_list)) == 0); +} + +inline bool operator!=(const TsyncEthTimeDomain& lhs, const TsyncEthTimeDomain& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(const TsyncTimeDomainConfig& lhs, const TsyncTimeDomainConfig& rhs) { + return ((lhs.domain_id == rhs.domain_id) && + (lhs.consumer_config == rhs.consumer_config) && + (lhs.provider_config == rhs.provider_config) && + lhs.eth_time_domain == rhs.eth_time_domain && + lhs.debounce_time == rhs.debounce_time && + lhs.sync_loss_timeout == rhs.sync_loss_timeout); +} + + +inline bool operator!=(const TsyncTimeDomainConfig& lhs, const TsyncTimeDomainConfig& rhs) { + return !(lhs == rhs); +} + +constexpr bool operator==(const TimestampWithStatus& v1, const TimestampWithStatus& v2) { + return ((v1.status == v2.status) && (v1.nanoseconds == v2.nanoseconds) && (v1.seconds == v2.seconds)); +} + +constexpr bool operator!=(const TimestampWithStatus& v1, const TimestampWithStatus& v2) { + return (!(v1 == v2)); +} + +constexpr bool operator==(const VirtualLocalTime& v1, const VirtualLocalTime& v2) { + return (v1 == v2); +} + +constexpr bool operator!=(const VirtualLocalTime& v1, const VirtualLocalTime& v2) { + return (!(v1 == v2)); +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_MATCHER_OPERATORS_H_ diff --git a/src/tsync-utility-lib/test/mocks/BUILD b/src/tsync-utility-lib/test/mocks/BUILD new file mode 100644 index 0000000..27bc300 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/BUILD @@ -0,0 +1,52 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "utility_mocks", + srcs = [ + "SysCallsMiscMock.cpp", + "SysCallsNamedSemMock.cpp", + "SysCallsReadWriteLockMock.cpp", + "SysCallsShMemMock.cpp", + "TimeBaseReaderFactoryMock.cpp", + "TimeBaseWriterFactoryMock.cpp", + ], + hdrs = [ + "SharedMemTimeBaseReaderMock.h", + "SharedMemTimeBaseWriterMock.h", + "SysCallsMiscMock.h", + "SysCallsNamedSemMock.h", + "SysCallsReadWriteLockMock.h", + "SysCallsShMemMock.h", + "TimeBaseReaderFactoryMock.h", + "TimeBaseWriterFactoryMock.h", + ], + strip_include_prefix = ".", + deps = [ + "//src/tsync-utility-lib:tsync_utility_if", + "//src/tsync-utility-lib:utility_tsync_read_write_lock", + "//src/tsync-utility-lib:utility_shared_mem_time_base_reader", + "//src/tsync-utility-lib:utility_shared_mem_time_base_writer", + "@googletest//:gtest", + ], + visibility = [ + "//src:__subpackages__", + ], +) + +cc_library( + name = "utility_tsync_shared_utils_mock", + srcs = [ + "TsyncSharedUtilsMock.cpp", + ], + hdrs = [ + "TsyncSharedUtilsMock.h", + ], + strip_include_prefix = ".", + deps = [ + "//src/tsync-utility-lib:tsync_utility_if", + "@googletest//:gtest", + ], + visibility = [ + "//src:__subpackages__", + ], +) diff --git a/src/tsync-utility-lib/test/mocks/SharedMemTimeBaseReaderMock.h b/src/tsync-utility-lib/test/mocks/SharedMemTimeBaseReaderMock.h new file mode 100644 index 0000000..393c973 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SharedMemTimeBaseReaderMock.h @@ -0,0 +1,107 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SHAREDMEMTIMEBASEREADERMOCK_H_ +#define SCORE_TIME_SHAREDMEMTIMEBASEREADERMOCK_H_ + +#include + +#include "score/time/utility/TsyncConfigTypes.h" +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/ITimeBaseReader.h" + +namespace score { +namespace time { + +class TsyncConsumerConfig; +class TsyncProviderConfig; + +class SharedMemTimeBaseReaderMock : public ITimeBaseAccessor, public ITimeBaseReader { +public: + explicit SharedMemTimeBaseReaderMock(std::string_view /*name*/, uint32_t /* max_size */) noexcept { + // Little hack to help with the TSyncWorker UTs. + // The only purpose of this is to set the global sync status. + // This can always be overwritten by individual tests. + ON_CALL(*this, Read(testing::An())) + .WillByDefault( + testing::DoAll(testing::SetArgReferee<0>(SynchronizationStatus::kSynchronized), testing::Return(true))); + + // config injection + reader_mock_cfg_.consumer_config.time_slave_config.is_valid = true; + ON_CALL(*this, Read(testing::An())) + .WillByDefault(testing::DoAll(testing::SetArgReferee<0>(reader_mock_cfg_), testing::Return(true))); + + ON_CALL(*this, GetAccessor()).WillByDefault(testing::ReturnRef(*this)); + + // Have most boolean methods return success by default + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Read(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Skip(testing::_)).WillByDefault(testing::Return(true)); + ON_CALL(*this, SetPosition(testing::_)).WillByDefault(testing::Return(true)); + ON_CALL(*this, GetPosition()).WillByDefault(testing::Return(48)); + ON_CALL(*this, GetMaxSize()).WillByDefault(testing::Return(4096)); + ON_CALL(*this, GetState()).WillByDefault(testing::Return(ITimeBaseAccessor::State::Open)); + } + ~SharedMemTimeBaseReaderMock() noexcept { + Close(); + } + SharedMemTimeBaseReaderMock() = delete; + SharedMemTimeBaseReaderMock(const SharedMemTimeBaseReaderMock&) = delete; + SharedMemTimeBaseReaderMock& operator=(const SharedMemTimeBaseReaderMock&) = delete; + + // ITimeBaseAccessor + MOCK_METHOD0(Open, void()); + MOCK_METHOD0(Close, void()); + MOCK_CONST_METHOD0(GetName, std::string_view()); + MOCK_CONST_METHOD0(GetState, State()); + + // ITimeBaseReader + MOCK_METHOD0(GetAccessor, ITimeBaseAccessor&()); + + MOCK_METHOD1(Read, bool(std::uint8_t&)); + MOCK_METHOD1(Read, bool(std::uint16_t&)); + MOCK_METHOD1(Read, bool(std::uint32_t&)); + MOCK_METHOD1(Read, bool(std::uint64_t&)); + MOCK_METHOD1(Read, bool(std::int8_t&)); + MOCK_METHOD1(Read, bool(std::int16_t&)); + MOCK_METHOD1(Read, bool(std::int32_t&)); + MOCK_METHOD1(Read, bool(std::int64_t&)); + MOCK_METHOD1(Read, bool(float&)); + MOCK_METHOD1(Read, bool(double&)); + MOCK_METHOD1(Read, bool(std::string&)); + MOCK_METHOD1(Read, bool(std::string_view&)); + MOCK_METHOD2(Read, bool(const char**, std::size_t)); + MOCK_METHOD1(Read, bool(TimestampWithStatus&)); + MOCK_METHOD1(Read, bool(VirtualLocalTime&)); + MOCK_METHOD1(Read, bool(UserDataView&)); + MOCK_METHOD1(Read, bool(SynchronizationStatus&)); + MOCK_METHOD1(Read, bool(TsyncTimeDomainConfig&)); + MOCK_METHOD1(Read, bool(TsyncConsumerConfig&)); + MOCK_METHOD1(Read, bool(TsyncProviderConfig&)); + MOCK_METHOD1(Skip, bool(std::size_t)); + MOCK_METHOD1(SetPosition, bool(std::size_t)); + MOCK_CONST_METHOD0(GetPosition, std::size_t()); + MOCK_CONST_METHOD0(GetMaxSize, std::size_t()); + MOCK_METHOD0(lock, void()); + MOCK_METHOD0(unlock, void()); + +private: + TsyncTimeDomainConfig reader_mock_cfg_; +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SHAREDMEMTIMEBASEREADERMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/SharedMemTimeBaseWriterMock.h b/src/tsync-utility-lib/test/mocks/SharedMemTimeBaseWriterMock.h new file mode 100644 index 0000000..d3ea5c1 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SharedMemTimeBaseWriterMock.h @@ -0,0 +1,105 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SHAREDMEMTIMEBASEWRITERMOCK_H_ +#define SCORE_TIME_SHAREDMEMTIMEBASEWRITERMOCK_H_ + +#include + +#include + +#include "score/span.hpp" + +#include "score/time/utility/ITimeBaseAccessor.h" +#include "score/time/utility/ITimeBaseWriter.h" + +namespace score { +namespace time { + +class TsyncConsumerConfig; +class TsyncProviderConfig; +class SharedMemTimeBaseWriterMock : public ITimeBaseAccessor, public ITimeBaseWriter { +public: + SharedMemTimeBaseWriterMock(std::string_view, std::size_t, bool) { + ON_CALL(*this, GetAccessor()).WillByDefault(testing::ReturnRef(*this)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + + // Have most boolean methods return success by default + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An>())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, Write(testing::An())).WillByDefault(testing::Return(true)); + ON_CALL(*this, WriteDefaults()).WillByDefault(testing::Return(true)); + ON_CALL(*this, Skip(testing::_)).WillByDefault(testing::Return(true)); + ON_CALL(*this, SetPosition(testing::_)).WillByDefault(testing::Return(true)); + ON_CALL(*this, GetPosition()).WillByDefault(testing::Return(48)); + ON_CALL(*this, GetMaxSize()).WillByDefault(testing::Return(4096)); + ON_CALL(*this, GetState()).WillByDefault(testing::Return(ITimeBaseAccessor::State::Open)); + }; + + ~SharedMemTimeBaseWriterMock() { + Close(); + }; + + SharedMemTimeBaseWriterMock() = delete; + SharedMemTimeBaseWriterMock(const SharedMemTimeBaseWriterMock&) = delete; + SharedMemTimeBaseWriterMock& operator=(const SharedMemTimeBaseWriterMock&) = delete; + + // ITimeBaseAccessor + MOCK_METHOD0(Open, void()); + MOCK_METHOD0(Close, void()); + MOCK_CONST_METHOD0(GetName, std::string_view()); + MOCK_CONST_METHOD0(GetState, State()); + + // ITimeBaseWriter + MOCK_METHOD0(GetAccessor, ITimeBaseAccessor&()); + MOCK_METHOD1(Write, bool(std::uint8_t)); + MOCK_METHOD1(Write, bool(std::uint16_t)); + MOCK_METHOD1(Write, bool(std::uint32_t)); + MOCK_METHOD1(Write, bool(std::uint64_t)); + MOCK_METHOD1(Write, bool(std::int8_t)); + MOCK_METHOD1(Write, bool(std::int16_t)); + MOCK_METHOD1(Write, bool(std::int32_t)); + MOCK_METHOD1(Write, bool(std::int64_t)); + MOCK_METHOD1(Write, bool(float)); + MOCK_METHOD1(Write, bool(double)); + MOCK_METHOD1(Write, bool(const std::string&)); + MOCK_METHOD1(Write, bool(std::string_view)); + MOCK_METHOD1(Write, bool(score::cpp::span)); + MOCK_METHOD1(Write, bool(const TimestampWithStatus&)); + MOCK_METHOD1(Write, bool(const VirtualLocalTime&)); + MOCK_METHOD1(Write, bool(UserData)); + MOCK_METHOD1(Write, bool(UserDataView)); + MOCK_METHOD1(Write, bool(const SynchronizationStatus&)); + MOCK_METHOD1(Write, bool(const TsyncTimeDomainConfig&)); + MOCK_METHOD1(Write, bool(const TsyncConsumerConfig&)); + MOCK_METHOD1(Write, bool(const TsyncProviderConfig&)); + MOCK_METHOD2(Write, bool(const char*, std::size_t)); + MOCK_METHOD0(WriteDefaults, bool()); + MOCK_METHOD1(Skip, bool(std::size_t)); + MOCK_METHOD1(SetPosition, bool(std::size_t)); + MOCK_CONST_METHOD0(GetPosition, std::size_t()); + MOCK_CONST_METHOD0(GetMaxSize, std::size_t()); + MOCK_METHOD0(lock, void()); + MOCK_METHOD0(unlock, void()); +}; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SHAREDMEMTIMEBASEWRITERMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/SysCallsMiscMock.cpp b/src/tsync-utility-lib/test/mocks/SysCallsMiscMock.cpp new file mode 100644 index 0000000..5a98b0c --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsMiscMock.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SysCallsMiscMock.h" + +namespace score { +namespace time { + +std::unique_ptr<::testing::NiceMock> misc_mock; + +mode_t OsUmask(mode_t mode) { + return misc_mock->OsUmask(mode); +} + +int OsClockGetTime(clockid_t clk_id, timespec* tp) { + return misc_mock->OsClockGetTime(clk_id, tp); +} + +int OsThreadCreate(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg) { + return misc_mock->OsThreadCreate(thread, attr, start_routine, arg); +} + +int OsThreadJoin(pthread_t thread, void** retval) { + return misc_mock->OsThreadJoin(thread, retval); +} + + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/SysCallsMiscMock.h b/src/tsync-utility-lib/test/mocks/SysCallsMiscMock.h new file mode 100644 index 0000000..c9e4200 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsMiscMock.h @@ -0,0 +1,31 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYSCALLSMISCMOCK_H_ +#define SCORE_TIME_SYSCALLSMISCMOCK_H_ + +#include +#include + +namespace score { +namespace time { + +class SysCallsMiscMock { +public: + MOCK_METHOD1(OsUmask, mode_t(mode_t)); + MOCK_METHOD2(OsClockGetTime, int(clockid_t, timespec*)); + MOCK_METHOD4(OsThreadCreate, + int(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg)); + MOCK_METHOD2(OsThreadJoin, int(pthread_t thread, void** retval)); + + ~SysCallsMiscMock() = default; +}; + +// Declare the mock object and initialize it in the test module +extern std::unique_ptr<::testing::NiceMock> misc_mock; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSCALLSMISCMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/SysCallsNamedSemMock.cpp b/src/tsync-utility-lib/test/mocks/SysCallsNamedSemMock.cpp new file mode 100644 index 0000000..afe62f9 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsNamedSemMock.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SysCallsNamedSemMock.h" + +#include +#include +#include +#include + +namespace score { +namespace time { + +std::unique_ptr<::testing::NiceMock> named_semaphore_mock; + +sem_t* OsSemOpen(const char* name, int oflag) { + return named_semaphore_mock->OsSemOpen(name, oflag); +} + +sem_t* OsSemOpen(const char* name, int oflag, mode_t mode, unsigned int value) { + return named_semaphore_mock->OsSemOpen(name, oflag, mode, value); +} + +int OsSemClose(sem_t* sem) { + return named_semaphore_mock->OsSemClose(sem); +} + +int OsSemUnlink(const char* name) { + return named_semaphore_mock->OsSemUnlink(name); +} + +int OsSemWait(sem_t* sem) { + return named_semaphore_mock->OsSemWait(sem); +} + +int OsSemPost(sem_t* sem) { + return named_semaphore_mock->OsSemPost(sem); +} + +int OsSemTryWait(sem_t* sem) { + return named_semaphore_mock->OsSemTryWait(sem); +} + +int OsSemGetValue(sem_t* sem, int* value) { + return named_semaphore_mock->OsSemGetValue(sem, value); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/SysCallsNamedSemMock.h b/src/tsync-utility-lib/test/mocks/SysCallsNamedSemMock.h new file mode 100644 index 0000000..46a949d --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsNamedSemMock.h @@ -0,0 +1,50 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYSCALLSNAMEDSEMMOCK_H_ +#define SCORE_TIME_SYSCALLSNAMEDSEMMOCK_H_ + +#include +#include + +#include + +namespace score { +namespace time { + +static sem_t SEM_DUMMY; + +class SysCallsNamedSemMock { +public: + MOCK_METHOD2(OsSemOpen, sem_t*(const char*, int)); + MOCK_METHOD4(OsSemOpen, sem_t*(const char*, int, mode_t, unsigned int)); + MOCK_METHOD1(OsSemClose, int(sem_t*)); + MOCK_METHOD1(OsSemUnlink, int(const char*)); + MOCK_METHOD1(OsSemWait, int(sem_t*)); + MOCK_METHOD1(OsSemPost, int(sem_t*)); + MOCK_METHOD1(OsSemTryWait, int(sem_t*)); + MOCK_METHOD2(OsSemGetValue, int(sem_t*, int*)); + + SysCallsNamedSemMock() { + using ::testing::_; + using ::testing::Return; + ON_CALL(*this, OsSemOpen(_, _)).WillByDefault(Return(&SEM_DUMMY)); + ON_CALL(*this, OsSemOpen(_, _, _, _)).WillByDefault(Return(&SEM_DUMMY)); + ON_CALL(*this, OsSemClose(_)).WillByDefault(Return(0)); + ON_CALL(*this, OsSemUnlink(_)).WillByDefault(Return(0)); + ON_CALL(*this, OsSemWait(_)).WillByDefault(Return(0)); + ON_CALL(*this, OsSemTryWait(_)).WillByDefault(Return(0)); + ON_CALL(*this, OsSemPost(_)).WillByDefault(Return(0)); + ON_CALL(*this, OsSemGetValue(_, _)).WillByDefault(Return(0)); + } + ~SysCallsNamedSemMock() = default; +}; + +// Declare the mock object and initialize it in the test module +extern std::unique_ptr<::testing::NiceMock> named_semaphore_mock; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSCALLSNAMEDSEMMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/SysCallsReadWriteLockMock.cpp b/src/tsync-utility-lib/test/mocks/SysCallsReadWriteLockMock.cpp new file mode 100644 index 0000000..a93b258 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsReadWriteLockMock.cpp @@ -0,0 +1,53 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SysCallsReadWriteLockMock.h" + +namespace score { +namespace time { + +std::unique_ptr> rw_lock_mock; + +int OsRwLockInitAttr(pthread_rwlockattr_t* attr) { + return rw_lock_mock->OsRwLockInitAttr(attr); +} + +int OsRwLockDestroyAttr(pthread_rwlockattr_t* attr) { + return rw_lock_mock->OsRwLockDestroyAttr(attr); +} + +int OsRwLockAttrSetPShared(pthread_rwlockattr_t* attr, int shared) { + return rw_lock_mock->OsRwLockAttrSetPShared(attr, shared); +} + +int OsRwLockInit(pthread_rwlock_t* rw_lock, const pthread_rwlockattr_t* attr) { + return rw_lock_mock->OsRwLockInit(rw_lock, attr); +} + +int OsRwLockTryReadLock(pthread_rwlock_t* rw_lock) { + return rw_lock_mock->OsRwLockTryReadLock(rw_lock); +} + +int OsRwLockReadLock(pthread_rwlock_t* rw_lock) { + return rw_lock_mock->OsRwLockReadLock(rw_lock); +} + +int OsRwLockTryWriteLock(pthread_rwlock_t* rw_lock) { + return rw_lock_mock->OsRwLockTryWriteLock(rw_lock); +} + +int OsRwLockWriteLock(pthread_rwlock_t* rw_lock) { + return rw_lock_mock->OsRwLockWriteLock(rw_lock); +} + +int OsRwLockUnlock(pthread_rwlock_t* rw_lock) { + return rw_lock_mock->OsRwLockUnlock(rw_lock); +} + +int OsRwLockDestroyLock(pthread_rwlock_t* rw_lock) { + return rw_lock_mock->OsRwLockDestroyLock(rw_lock); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/SysCallsReadWriteLockMock.h b/src/tsync-utility-lib/test/mocks/SysCallsReadWriteLockMock.h new file mode 100644 index 0000000..754c217 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsReadWriteLockMock.h @@ -0,0 +1,55 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYSCALLREADWRITELOCKMOCK_H_ +#define SCORE_TIME_SYSCALLREADWRITELOCKMOCK_H_ + +#include +#include +#include + +namespace score { +namespace time { + +class SysCallsReadWriteLockMock { +public: + SysCallsReadWriteLockMock() { + ON_CALL(*this, OsRwLockInitAttr(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockDestroyAttr(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockAttrSetPShared(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockInit(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + + ON_CALL(*this, OsRwLockTryReadLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockReadLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockTryWriteLock(::testing::_)).WillByDefault(::testing::Return(0)); + + ON_CALL(*this, OsRwLockTryWriteLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockWriteLock(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsRwLockUnlock(::testing::_)).WillByDefault(::testing::Return(0)); + + ON_CALL(*this, OsUmask(::testing::_)).WillByDefault(::testing::Return(0)); + } + + MOCK_METHOD1(OsRwLockInitAttr, int(pthread_rwlockattr_t*)); + MOCK_METHOD1(OsRwLockDestroyAttr, int(pthread_rwlockattr_t*)); + MOCK_METHOD2(OsRwLockAttrSetPShared, int(pthread_rwlockattr_t*, int)); + MOCK_METHOD2(OsRwLockInit, int(pthread_rwlock_t*, const pthread_rwlockattr_t*)); + MOCK_METHOD1(OsRwLockTryReadLock, int(pthread_rwlock_t*)); + MOCK_METHOD1(OsRwLockReadLock, int(pthread_rwlock_t*)); + MOCK_METHOD1(OsRwLockTryWriteLock, int(pthread_rwlock_t*)); + MOCK_METHOD1(OsRwLockWriteLock, int(pthread_rwlock_t*)); + MOCK_METHOD1(OsRwLockUnlock, int(pthread_rwlock_t*)); + MOCK_METHOD1(OsRwLockDestroyLock, int(pthread_rwlock_t*)); + MOCK_METHOD1(OsUmask, mode_t(mode_t)); + + ~SysCallsReadWriteLockMock() = default; +}; + +// Declare the mock object and initialize it in the test module +extern std::unique_ptr<::testing::NiceMock> rw_lock_mock; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSCALLREADWRITELOCKMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/SysCallsShMemMock.cpp b/src/tsync-utility-lib/test/mocks/SysCallsShMemMock.cpp new file mode 100644 index 0000000..7ae4a5b --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsShMemMock.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "SysCallsShMemMock.h" + +namespace score { +namespace time { + +std::unique_ptr<::testing::NiceMock> shared_mem_mock; + +int OsShmOpen(const char* __name, int __oflag, mode_t __mode) { + return shared_mem_mock->OsShmOpen(__name, __oflag, __mode); +} + +int OsShmUnlink(const char* name) { + return shared_mem_mock->OsShmUnlink(name); +} + +int OsFtruncate(int __fd, off_t __length) { + return shared_mem_mock->OsFtruncate(__fd, __length); +} + +void* OsMmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset) { + return shared_mem_mock->OsMmap(__addr, __len, __prot, __flags, __fd, __offset); +} + +int OsMunmap(void* addr, size_t len) { + return shared_mem_mock->OsMunmap(addr, len); +} + +int OsClose(int __fd) { + if (shared_mem_mock) { + return shared_mem_mock->OsClose(__fd); + } else { + return 0; + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/SysCallsShMemMock.h b/src/tsync-utility-lib/test/mocks/SysCallsShMemMock.h new file mode 100644 index 0000000..8687ffc --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/SysCallsShMemMock.h @@ -0,0 +1,42 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_SYSCALLSSHMEMMOCK_H_ +#define SCORE_TIME_SYSCALLSSHMEMMOCK_H_ + +#include +#include + +#include + +namespace score { +namespace time { + +class SysCallsShMemMock { +public: + SysCallsShMemMock() { + ON_CALL(*this, OsShmOpen(::testing::_, ::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsShmUnlink(::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsFtruncate(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsMmap(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)).WillByDefault(::testing::Return(nullptr)); + ON_CALL(*this, OsMunmap(::testing::_, ::testing::_)).WillByDefault(::testing::Return(0)); + ON_CALL(*this, OsClose(::testing::_)).WillByDefault(::testing::Return(0)); + } + MOCK_METHOD3(OsShmOpen, int(const char*, int, mode_t)); + MOCK_METHOD1(OsShmUnlink, int(const char*)); + MOCK_METHOD2(OsFtruncate, int(int, off_t)); + MOCK_METHOD6(OsMmap, void*(void*, size_t, int, int, int, off_t)); + MOCK_METHOD2(OsMunmap, int(void*, size_t)); + MOCK_METHOD1(OsClose, int(int)); + + ~SysCallsShMemMock() = default; +}; + +// Declare the mock object and initialize it in the test module +extern std::unique_ptr<::testing::NiceMock> shared_mem_mock; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_SYSCALLSSHMEMMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/TimeBaseReaderFactoryMock.cpp b/src/tsync-utility-lib/test/mocks/TimeBaseReaderFactoryMock.cpp new file mode 100644 index 0000000..ae695a2 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/TimeBaseReaderFactoryMock.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "TimeBaseReaderFactoryMock.h" + +#include "score/time/utility/TimeBaseReaderFactory.h" +#include "SharedMemTimeBaseReaderMock.h" +#include "SharedMemTimeBaseReader.h" + +namespace score { +namespace time { + +constexpr size_t kTimeBaseShmemFileSize{4096u}; +std::unique_ptr<::testing::NiceMock> reader_factory_mock; +bool reader_factory_mock_return_real_reader = false; + +std::unique_ptr TimeBaseReaderFactory::Create(std::string_view name) { + return TimeBaseReaderFactory::Create(name, kTimeBaseShmemFileSize); +} + +std::unique_ptr TimeBaseReaderFactory::Create(std::string_view name, std::size_t max_size) { + if (reader_factory_mock_return_real_reader) { + return std::make_unique(name, max_size); + } else { + return reader_factory_mock->Create(name, max_size); + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/TimeBaseReaderFactoryMock.h b/src/tsync-utility-lib/test/mocks/TimeBaseReaderFactoryMock.h new file mode 100644 index 0000000..f0ad6f6 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/TimeBaseReaderFactoryMock.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_TIMEBASEREADERFACTORYMOCK_H_ +#define SCORE_TIME_TIMEBASEREADERFACTORYMOCK_H_ + +#include +#include + +#include + +#include "SharedMemTimeBaseReaderMock.h" + +namespace score { +namespace time { + +class TimeBaseReaderFactoryMock { +public: + TimeBaseReaderFactoryMock() { + ON_CALL(*this, Create).WillByDefault([](std::string_view name, std::size_t max_size) { + return std::make_unique<::testing::NiceMock>(name, max_size); + }); + } + + ~TimeBaseReaderFactoryMock() = default; + + MOCK_METHOD2(Create, std::unique_ptr(std::string_view, std::size_t)); +}; + +extern std::unique_ptr<::testing::NiceMock> reader_factory_mock; +extern bool reader_factory_mock_return_real_reader; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_TIMEBASEREADERFACTORYMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/TimeBaseWriterFactoryMock.cpp b/src/tsync-utility-lib/test/mocks/TimeBaseWriterFactoryMock.cpp new file mode 100644 index 0000000..856546a --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/TimeBaseWriterFactoryMock.cpp @@ -0,0 +1,32 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "TimeBaseWriterFactoryMock.h" + +#include "score/time/utility/TimeBaseWriterFactory.h" +#include "SharedMemTimeBaseWriterMock.h" +#include "SharedMemTimeBaseWriter.h" + +namespace score { +namespace time { + +constexpr size_t kTimeBaseShmemFileSize{4096u}; +std::unique_ptr<::testing::NiceMock> writer_factory_mock; +bool writer_factory_mock_return_real_writer = false; + +std::unique_ptr TimeBaseWriterFactory::Create(std::string_view name, bool is_owner) { + return TimeBaseWriterFactory::Create(name, kTimeBaseShmemFileSize, is_owner); +} + +std::unique_ptr TimeBaseWriterFactory::Create(std::string_view name, std::size_t max_size, + bool is_owner) { + if (writer_factory_mock_return_real_writer) { + return std::make_unique(name, max_size, is_owner); + } else { + return writer_factory_mock->Create(name, max_size, is_owner); + } +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/TimeBaseWriterFactoryMock.h b/src/tsync-utility-lib/test/mocks/TimeBaseWriterFactoryMock.h new file mode 100644 index 0000000..4a53ef8 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/TimeBaseWriterFactoryMock.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_TIMEBASEWRITERFACTORYMOCK_H_ +#define SCORE_TIME_TIMEBASEWRITERFACTORYMOCK_H_ + +#include +#include + +#include + +#include "SharedMemTimeBaseWriterMock.h" + +namespace score { +namespace time { + +class TimeBaseWriterFactoryMock { +public: + TimeBaseWriterFactoryMock() { + ON_CALL(*this, Create).WillByDefault([](std::string_view name, std::size_t max_size, bool is_owner) { + return std::make_unique<::testing::NiceMock>(name, max_size, is_owner); + }); + } + + ~TimeBaseWriterFactoryMock() = default; + + MOCK_METHOD3(Create, std::unique_ptr(std::string_view, std::size_t, bool)); +}; + +extern std::unique_ptr<::testing::NiceMock> writer_factory_mock; +extern bool writer_factory_mock_return_real_writer; + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_TIMEBASEWRITERFACTORYMOCK_H_ diff --git a/src/tsync-utility-lib/test/mocks/TsyncSharedUtilsMock.cpp b/src/tsync-utility-lib/test/mocks/TsyncSharedUtilsMock.cpp new file mode 100644 index 0000000..3d9fdf4 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/TsyncSharedUtilsMock.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "TsyncSharedUtilsMock.h" + +#include + +#include + +#include "score/time/utility/TsyncSharedUtils.h" + +namespace score { +namespace time { + +std::unique_ptr<::testing::NiceMock> shared_utils_mock; + +std::string TsyncSharedUtils::GetTransmissionSemaphoreName(std::uint32_t time_domain_id) { + return shared_utils_mock->GetTransmissionSemaphoreName(time_domain_id); +} + +TsyncNamedSemaphore TsyncSharedUtils::CreateTransmissionSemaphore(std::uint32_t time_domain_id, bool as_owner) { + return shared_utils_mock->CreateTransmissionSemaphore(time_domain_id, as_owner); +} + +std::optional TsyncSharedUtils::GetCurrentVirtualLocalTime() { + return shared_utils_mock->GetCurrentVirtualLocalTime(); +} + +} // namespace time +} // namespace score diff --git a/src/tsync-utility-lib/test/mocks/TsyncSharedUtilsMock.h b/src/tsync-utility-lib/test/mocks/TsyncSharedUtilsMock.h new file mode 100644 index 0000000..907ef48 --- /dev/null +++ b/src/tsync-utility-lib/test/mocks/TsyncSharedUtilsMock.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef TSYNC_SHARED_UTILS_MOCK_H_ +#define TSYNC_SHARED_UTILS_MOCK_H_ + +#include +#include + +#include + +// #include "score/time/utility/TsyncSharedUtils.h" +#include "score/time/utility/TsyncNamedSemaphore.h" + +namespace score { +namespace time { + +class TsyncSharedUtilsMock { +public: + MOCK_METHOD1(GetTransmissionSemaphoreName, std::string(std::uint32_t)); + MOCK_METHOD2(CreateTransmissionSemaphore, TsyncNamedSemaphore(std::uint32_t, bool)); + MOCK_METHOD0(GetCurrentVirtualLocalTime, std::optional()); + + TsyncSharedUtilsMock() { + ON_CALL(*this, CreateTransmissionSemaphore).WillByDefault([](std::uint32_t domain_id, bool is_owner) { +std::cerr << "SharedUtilsMock::CreateSem\n"; + return TsyncNamedSemaphore( + std::string("time_domain_") + std::to_string(static_cast(domain_id)), + TsyncNamedSemaphore::OpenMode::Unsignaled, is_owner); + }); + ON_CALL(*this, GetCurrentVirtualLocalTime).WillByDefault(testing::Return(std::nullopt)); + } +}; + +extern std::unique_ptr<::testing::NiceMock> shared_utils_mock; + +} // namespace time +} // namespace score + +#endif diff --git a/tests/SCORE_UT/BUILD b/tests/SCORE_UT/BUILD new file mode 100644 index 0000000..42e06c7 --- /dev/null +++ b/tests/SCORE_UT/BUILD @@ -0,0 +1 @@ +# Unit tests moved into component folders diff --git a/tests/common/matchers/matcher_operators.h b/tests/common/matchers/matcher_operators.h new file mode 100644 index 0000000..dd9cbe7 --- /dev/null +++ b/tests/common/matchers/matcher_operators.h @@ -0,0 +1,129 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_MATCHER_OPERATORS_H_ +#define SCORE_TIME_MATCHER_OPERATORS_H_ + +#include "score/span.hpp" +#include "tsync-ptp-lib/tsync_ptp_lib_types.h" +#include "score/time/utility/TsyncConfigTypes.h" +#include "score/time/utility/ITimeBaseAccessor.h" + +constexpr bool operator==(const TSync_TimeStampType& v1, const TSync_TimeStampType& v2) { + return ((v1.timeBaseStatus == v2.timeBaseStatus) && (v1.secondsHi == v2.secondsHi) && (v1.seconds == v2.seconds) && + (v1.nanoseconds == v2.nanoseconds)); +} + +constexpr bool operator!=(const TSync_TimeStampType& v1, const TSync_TimeStampType& v2) { + return (!(v1 == v2)); +} + +constexpr bool operator==(const TSync_VirtualLocalTimeType& v1, const TSync_VirtualLocalTimeType& v2) { + return ((v1.nanosecondsHi == v2.nanosecondsHi) && (v1.nanosecondsLo == v2.nanosecondsLo)); +} + +constexpr bool operator!=(const TSync_VirtualLocalTimeType& v1, const TSync_VirtualLocalTimeType& v2) { + return (!(v1 == v2)); +} + +constexpr bool operator==(const TSync_UserDataType& v1, const TSync_UserDataType& v2) { + return (v1.userDataLength == v2.userDataLength && v1.userByte0 == v2.userByte0 && v1.userByte1 == v2.userByte1 && + v1.userByte2 == v2.userByte2); +} + +constexpr bool operator!=(const TSync_UserDataType& v1, const TSync_UserDataType& v2) { + return (!(v1 == v2)); +} + +namespace score { +namespace core { + +inline bool operator==(const score::cpp::span& v1, const score::cpp::span& v2) { + return (std::equal(std::begin(v1), std::end(v1), std::begin(v2), std::end(v2))); +} + +inline bool operator!=(const score::cpp::span& v1, const score::cpp::span& v2) { + return (!(v1 == v2)); +} + +} // namespace core + +namespace time { +inline bool operator==(const TsyncConsumerConfig& lhs, const TsyncConsumerConfig& rhs) { + return ((lhs.name == rhs.name) && + (lhs.time_slave_config.is_valid == rhs.time_slave_config.is_valid) && (lhs.time_slave_config.follow_up_timeout_value == rhs.time_slave_config.follow_up_timeout_value) && + (lhs.time_slave_config.time_leap_future_threshold == rhs.time_slave_config.time_leap_future_threshold) && + (lhs.time_slave_config.time_leap_healing_counter == rhs.time_slave_config.time_leap_healing_counter) && + (lhs.time_slave_config.time_leap_past_threshold == rhs.time_slave_config.time_leap_past_threshold)); +} + +inline bool operator!=(const TsyncConsumerConfig& lhs, const TsyncConsumerConfig& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(const TsyncProviderConfig& lhs, const TsyncProviderConfig& rhs) { + return ((lhs.name == rhs.name) && + (lhs.time_master_config.is_valid == rhs.time_master_config.is_valid) && + (lhs.time_master_config.immediate_resume_time == rhs.time_master_config.immediate_resume_time) && + (lhs.time_master_config.is_system_wide_global_time_master == rhs.time_master_config.is_system_wide_global_time_master) && + (lhs.time_master_config.sync_period == rhs.time_master_config.sync_period) && + (lhs.time_sync_correction_config.is_valid == rhs.time_sync_correction_config.is_valid) && + (lhs.time_sync_correction_config.allow_provider_rate_correction == rhs.time_sync_correction_config.allow_provider_rate_correction) && + (lhs.time_sync_correction_config.num_rate_corrections_per_measurement_duration == rhs.time_sync_correction_config.num_rate_corrections_per_measurement_duration) && + (lhs.time_sync_correction_config.offset_correction_adaption_interval == rhs.time_sync_correction_config.offset_correction_adaption_interval) && + (lhs.time_sync_correction_config.offset_correction_jump_threshold == rhs.time_sync_correction_config.offset_correction_jump_threshold) && + (lhs.time_sync_correction_config.rate_deviation_measurement_duration == rhs.time_sync_correction_config.rate_deviation_measurement_duration)); +} + +inline bool operator!=(const TsyncProviderConfig& lhs, const TsyncProviderConfig& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(const TsyncEthTimeDomain& lhs, const TsyncEthTimeDomain& rhs) { + return ( lhs.is_valid == rhs.is_valid && + lhs.message_format == rhs.message_format && + lhs.num_fup_data_id_entries == rhs.num_fup_data_id_entries && + lhs.vlan_priority == rhs.vlan_priority && + memcmp(&lhs.dest_mac_address, &rhs.dest_mac_address, sizeof(lhs.dest_mac_address)) == 0 && + memcmp(&lhs.fup_data_id_list, &rhs.fup_data_id_list, sizeof(lhs.fup_data_id_list)) == 0); +} + +inline bool operator!=(const TsyncEthTimeDomain& lhs, const TsyncEthTimeDomain& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(const TsyncTimeDomainConfig& lhs, const TsyncTimeDomainConfig& rhs) { + return ((lhs.domain_id == rhs.domain_id) && + (lhs.consumer_config == rhs.consumer_config) && + (lhs.provider_config == rhs.provider_config) && + lhs.eth_time_domain == rhs.eth_time_domain && + lhs.debounce_time == rhs.debounce_time && + lhs.sync_loss_timeout == rhs.sync_loss_timeout); +} + + +inline bool operator!=(const TsyncTimeDomainConfig& lhs, const TsyncTimeDomainConfig& rhs) { + return !(lhs == rhs); +} + +constexpr bool operator==(const TimestampWithStatus& v1, const TimestampWithStatus& v2) { + return ((v1.status == v2.status) && (v1.nanoseconds == v2.nanoseconds) && (v1.seconds == v2.seconds)); +} + +constexpr bool operator!=(const TimestampWithStatus& v1, const TimestampWithStatus& v2) { + return (!(v1 == v2)); +} + +constexpr bool operator==(const VirtualLocalTime& v1, const VirtualLocalTime& v2) { + return (v1 == v2); +} + +constexpr bool operator!=(const VirtualLocalTime& v1, const VirtualLocalTime& v2) { + return (!(v1 == v2)); +} + +} // namespace time +} // namespace score + +#endif // SCORE_TIME_MATCHER_OPERATORS_H_ diff --git a/tests/common/mocks/include/AbortMock.h b/tests/common/mocks/include/AbortMock.h new file mode 100644 index 0000000..5b104e3 --- /dev/null +++ b/tests/common/mocks/include/AbortMock.h @@ -0,0 +1,27 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_MOCK_ABORTMOCK_H +#define SCORE_TIME_MOCK_ABORTMOCK_H + +#include + +namespace score { +namespace time { +namespace mock { + +class AbortMock +{ +public: + MOCK_METHOD1(logFatalAndAbort, void(const char*)); +}; + +// Declare the mock object and initialize it in the test module +extern std::unique_ptr<::testing::NiceMock> abort_mock; + +} // namespace mock +} // namespace time +} // namespace score + +#endif // SCORE_TIME_MOCK_ABORTMOCK_H diff --git a/tests/common/mocks/include/ConfigLoaderMock.h b/tests/common/mocks/include/ConfigLoaderMock.h new file mode 100644 index 0000000..3d58158 --- /dev/null +++ b/tests/common/mocks/include/ConfigLoaderMock.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_CONFIG_LOADER_MOCK_H_ +#define SCORE_TIME_CONFIG_LOADER_MOCK_H_ + +#include + +#include + +#include "ConfigLoader.h" + +namespace score { +namespace time { +class TsyncConfigLoaderMock { +public: + MOCK_CONST_METHOD0(GetConfig, const TimeBaseConfiguration&()); + MOCK_METHOD0(DumpConfig, void()); +}; + +extern std::unique_ptr config_loader_mock; + +ConfigLoader::ConfigLoader() { +} + +const TimeBaseConfiguration& ConfigLoader::GetConfig() const noexcept { + return config_loader_mock->GetConfig(); +} + +void ConfigLoader::DumpConfig() noexcept { + config_loader_mock->DumpConfig(); +} + +} /* namespace time */ +} /* namespace score */ + +#endif diff --git a/tests/common/mocks/include/TsyncWorkerMock.h b/tests/common/mocks/include/TsyncWorkerMock.h new file mode 100644 index 0000000..64212ab --- /dev/null +++ b/tests/common/mocks/include/TsyncWorkerMock.h @@ -0,0 +1,53 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef SCORE_TIME_WORKER_MOCK_H_ +#define SCORE_TIME_WORKER_MOCK_H_ +#include + +namespace score { +namespace time { + +// Mock class for score::time::TsyncWorker +class TsyncWorkerMock { +public: + MOCK_METHOD0(Init, void()); + MOCK_METHOD0(Run, void()); + MOCK_METHOD0(ShutDown, void()); +}; + +// Declare the mock object and initialize it in the test module +extern std::unique_ptr worker_mock; + +// Stub class for score::time::TsyncWorker +class TsyncWorker { +public: + TsyncWorker() noexcept; + ~TsyncWorker() noexcept = default; + TsyncWorker(const TsyncWorker&) = delete; + TsyncWorker& operator=(const TsyncWorker&) = delete; + void Init(void) noexcept; + void Run(void) noexcept; + void ShutDown(void) noexcept; +}; + +TsyncWorker::TsyncWorker() noexcept { +} + +void TsyncWorker::Init() noexcept { + worker_mock->Init(); +} + +void TsyncWorker::Run() noexcept { + worker_mock->Run(); +} + +void TsyncWorker::ShutDown() noexcept { + worker_mock->ShutDown(); +} + +} /* namespace time */ +} /* namespace score */ + +#endif /*SCORE_TIME_WORKER_MOCK_H_ */ diff --git a/tests/common/mocks/include/flatbuffers/flatbuffers.h b/tests/common/mocks/include/flatbuffers/flatbuffers.h new file mode 100644 index 0000000..26e9edb --- /dev/null +++ b/tests/common/mocks/include/flatbuffers/flatbuffers.h @@ -0,0 +1,122 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATBUFFERS_H_ +#define FLATBUFFERS_H_ + +#include +#include +#include +#include +#include + +namespace flatbuffers { +template +struct Offset { + Offset() { + data = std::make_shared(); + } + + T* operator->() { + return data.get(); + } + + const T* operator->() const { + return data.get(); + } + + operator T*() { + return data.get(); + } + + operator const T*() const { + return data.get(); + } + + std::shared_ptr data; +}; + +template +class Vector { +public: + Vector() : vector_() { + } + + Vector(std::vector vector) : vector_(vector) { + } + + T Get(std::size_t f_index) const { + return vector_[f_index]; + } + + void push_back(T f_param) { + vector_.push_back(f_param); + } + + T& operator[](std::size_t f_index) { + return vector_[f_index]; + } + std::size_t size() { + return vector_.size(); + } + + bool empty() { + return vector_.empty(); + } + + typename std::vector::iterator begin() { + return vector_.begin(); + } + + typename std::vector::iterator end() { + return vector_.end(); + } + + void clear() { + vector_.clear(); + } + + typename std::vector::value_type* data() { + return vector_.data(); + } + + void resize(size_t n, const typename std::vector::value_type& val) { + vector_.resize(n, val); + } + +private: + std::vector vector_; +}; + +class String { +public: + String(const char* str) : string_(str) { + } + + String(std::string& str) : string_(str) { + } + + const std::string& str() const { + return string_; + } + + const char* c_str() const { + return string_.c_str(); + } + + std::size_t Length() const { + return string_.length(); + } + + std::size_t size() const { + return string_.length(); + } + +private: + std::string string_; +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_H_ diff --git a/tests/common/mocks/include/flatcfg/flatcfg.h b/tests/common/mocks/include/flatcfg/flatcfg.h new file mode 100644 index 0000000..e956197 --- /dev/null +++ b/tests/common/mocks/include/flatcfg/flatcfg.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef FLATCFG_H_ +#define FLATCFG_H_ + +#include + +#include +#include +#include +#include + +#include "flatcfg/flatcfg_error.h" +#include "score/result/result.h" + +namespace flatcfg { +class Mock_Flatcfg { +public: + MOCK_METHOD0(getSwClusterList, score::Result>()); + MOCK_METHOD1(load, score::Result>(const std::string&)); +}; + +class FlatCfg { +public: + static Mock_Flatcfg* mock_flat_cfg_; + + template + FlatCfg(const T&) { + } + score::Result> getSwClusterList() { + return mock_flat_cfg_->getSwClusterList(); + } + score::Result> load(const std::string& swcl) { + return mock_flat_cfg_->load(swcl); + } +}; + +} // namespace flatcfg +#endif // FLATCFG_H_ diff --git a/tests/common/mocks/include/tsync-ptp-lib/SysCallsThreadMock.h b/tests/common/mocks/include/tsync-ptp-lib/SysCallsThreadMock.h new file mode 100644 index 0000000..3df67fe --- /dev/null +++ b/tests/common/mocks/include/tsync-ptp-lib/SysCallsThreadMock.h @@ -0,0 +1,31 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef TIMESYNC_PTP_LIB_SYSCALLSTHREADMOCK_H +#define TIMESYNC_PTP_LIB_SYSCALLSTHREADMOCK_H + +#include +#include + +namespace score { +namespace time { + +class SysCallsThreadMock { +public: + MOCK_METHOD4(OsThreadCreate, + int(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg)); + + MOCK_METHOD2(OsThreadJoin, int(pthread_t thread, void** retval)); + + ~SysCallsThreadMock() = default; +}; + +// Declare the mock object and initialize it in the test module +// extern std::unique_ptr<::testing::NiceMock> thread_mock; +extern std::unique_ptr< testing::NiceMock< SysCallsThreadMock> > thread_mock; + +} // namespace time +} // namespace score + +#endif // TIMESYNC_PTP_LIB_SYSCALLSTHREADMOCK_H diff --git a/tests/common/mocks/include/tsync-ptp-lib/SysCallsTimeMock.h b/tests/common/mocks/include/tsync-ptp-lib/SysCallsTimeMock.h new file mode 100644 index 0000000..0f98509 --- /dev/null +++ b/tests/common/mocks/include/tsync-ptp-lib/SysCallsTimeMock.h @@ -0,0 +1,27 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#ifndef TIMESYNC_PTP_LIB_SYSCALLSTIMEMOCK_H +#define TIMESYNC_PTP_LIB_SYSCALLSTIMEMOCK_H + +#include +#include + +namespace score { +namespace time { + +class SysCallsTimeMock { +public: + MOCK_METHOD2(OsClockGetTime, int(clockid_t clk_id, timespec* tp)); + + ~SysCallsTimeMock() = default; +}; + +// Declare the mock object and initialize it in the test module +// extern std::unique_ptr<::testing::NiceMock> time_mock; +extern std::unique_ptr< testing::NiceMock< SysCallsTimeMock> > time_mock; +} // namespace time +} // namespace score + +#endif // TIMESYNC_PTP_LIB_SYSCALLSTIMEMOCK_H diff --git a/tests/common/mocks/src/AbortMock.cpp b/tests/common/mocks/src/AbortMock.cpp new file mode 100644 index 0000000..4947c6c --- /dev/null +++ b/tests/common/mocks/src/AbortMock.cpp @@ -0,0 +1,18 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "AbortMock.h" + +namespace score { +namespace time { +namespace common { + +void logFatalAndAbort(const char* msg) noexcept { + mock::AbortMock abortMock; + abortMock.logFatalAndAbort(msg); +} + +} // namespace common +} // namespace core +} // namespace score diff --git a/tests/common/mocks/src/TsyncPtpLibMock/TsyncPtpLibMock.cpp b/tests/common/mocks/src/TsyncPtpLibMock/TsyncPtpLibMock.cpp new file mode 100644 index 0000000..a8e9106 --- /dev/null +++ b/tests/common/mocks/src/TsyncPtpLibMock/TsyncPtpLibMock.cpp @@ -0,0 +1,52 @@ +/******************************************************************************** + * (c) 2025 ETAS GmbH. All rights reserved. + ********************************************************************************/ + +#include "tsync_ptp_lib.h" + +#define TSYNC_TIMEBASE_SHMEM_SIZE 42 +static uint8_t tsync_timebase_data[TSYNC_TIMEBASE_SHMEM_SIZE]; + +extern "C" { + +TSync_VirtualLocalTimeType tsync_ptp_lib_mock_vlt_to_inject; +bool tsync_ptp_lib_mock_return_invalid_handle = false; +TSync_ReturnType tsync_transmit_global_time_return_value = E_NOT_OK; +TSync_ReturnType tsync_get_current_vlt_return_value = E_OK; + +TSync_ReturnType TSync_Open(void) { + return E_OK; +} + +void TSync_Close(void) { +} + +TSync_TimeBaseHandleType TSync_OpenTimebase(TSync_SynchronizedTimeBaseType timeBaseId) { + (void)timeBaseId; + if (tsync_ptp_lib_mock_return_invalid_handle) { + return TSYNC_INVALID_HANDLE; + } else { + TSync_TimeBaseHandleType h = static_cast(&tsync_timebase_data); + return h; + } +} + +void TSync_CloseTimebase(TSync_TimeBaseHandleType timeBaseHandle) { + (void)timeBaseHandle; +} + +TSync_ReturnType TSync_GetCurrentVirtualLocalTime(TSync_TimeBaseHandleType timeBaseHandle, + TSync_VirtualLocalTimeType* localTimePtr) { + (void)timeBaseHandle; + + localTimePtr->nanosecondsHi = tsync_ptp_lib_mock_vlt_to_inject.nanosecondsHi; + localTimePtr->nanosecondsLo = tsync_ptp_lib_mock_vlt_to_inject.nanosecondsLo; + + return tsync_get_current_vlt_return_value; +} + +TSync_ReturnType TSync_TransmitGlobalTime(TSync_TimeBaseHandleType timeBaseHandle) { + (void)timeBaseHandle; + return tsync_transmit_global_time_return_value; +} +} diff --git a/tests/cpp/BUILD b/tests/cpp/BUILD deleted file mode 100644 index e3b354e..0000000 --- a/tests/cpp/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -cc_test( - name = "cpp_test_main", - srcs = ["test_main.cpp"], - deps = [ - "@googletest//:gtest_main", # GoogleTest dependency via Bazel Modules - ], -) diff --git a/tests/cpp/test_main.cpp b/tests/cpp/test_main.cpp deleted file mode 100644 index f523107..0000000 --- a/tests/cpp/test_main.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************** -* Copyright (c) 2025 Contributors to the Eclipse Foundation -* -* See the NOTICE file(s) distributed with this work for additional -* information regarding copyright ownership. -* -* This program and the accompanying materials are made available under the -* terms of the Apache License Version 2.0 which is available at -* https://www.apache.org/licenses/LICENSE-2.0 -* -* SPDX-License-Identifier: Apache-2.0 -********************************************************************************/ -#include - -// Function to be tested -int add(int a, int b) { - return a + b; -} - -// Test case -TEST(AdditionTest, HandlesPositiveNumbers) { - EXPECT_EQ(add(2, 3), 5); - EXPECT_EQ(add(10, 20), 30); -} - -TEST(AdditionTest, HandlesNegativeNumbers) { - EXPECT_EQ(add(-2, -3), -5); - EXPECT_EQ(add(-10, 5), -5); -} - -TEST(AdditionTest, HandlesZero) { - EXPECT_EQ(add(0, 0), 0); - EXPECT_EQ(add(0, 5), 5); - EXPECT_EQ(add(5, 0), 5); -} - -// Main function for running tests -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/rust/BUILD b/tests/rust/BUILD deleted file mode 100644 index 828a001..0000000 --- a/tests/rust/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_test") - -rust_test( - name = "rust_hello_test", - srcs = ["test_main.rs"], -) diff --git a/tests/rust/test_main.rs b/tests/rust/test_main.rs deleted file mode 100644 index 9390d5e..0000000 --- a/tests/rust/test_main.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[test] -fn test_hello() { - assert_eq!(2 + 2, 4); -}