Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..5e3dd015f --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,6 @@ +coverage: + status: + patch: off + project: off +ignore: + - "test-binaries" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b28e90ae7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..110315e11 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,47 @@ +name: Generate documentation +on: + push: + branches: + - main + +jobs: + doc: + name: Document + runs-on: ubuntu-latest + permissions: + contents: write # To push a branch + pull-requests: write # To create a PR from that branch + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install latest mdbook + run: | + tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') + url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" + mkdir mdbook + curl -sSL $url | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + - name: Generate reference manual + run: mdbook build docs + - name: Generate API documentation + run: cargo +nightly doc --workspace --no-deps --all-features --target-dir docs/api + - name: Deploy GitHub Pages + run: | + cd docs + git worktree add gh-pages + git config user.name "Deploy from CI" + git config user.email "" + cd gh-pages + git update-ref -d refs/heads/gh-pages + rm -rf * + mv ../book/* . + mkdir docs + mv ../api/doc/* docs + git add . + git commit -m "Deploy $GITHUB_SHA to gh-pages" + git push --force --set-upstream origin gh-pages diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 000000000..13bc0fc2f --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,124 @@ +name: Test and Check + +on: + push: + branches: [ main ] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build no-std + uses: actions-rs/cargo@v1 + with: + command: build + toolchain: stable + args: --no-default-features + + - name: Build stm32 + working-directory: statime-stm32 + run: cargo build + + # Build std is handled by test job + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@v2 + with: + tool: cargo-llvm-cov + + - name: Run tests + run: cargo llvm-cov --all-features --lcov --output-path lcov.info + env: + RUST_BACKTRACE: 1 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: lcov.info + fail_ci_if_error: false + + check: + name: Check style + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt, clippy + override: true + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + toolchain: stable + args: --all --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + toolchain: stable + args: --workspace --all-features -- -D warnings + + - name: Run clippy (fuzzers) + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 + with: + command: clippy + args: --manifest-path ./fuzz/Cargo.toml --all-targets -- -D warnings + + fuzz: + name: Smoke-test fuzzing targets + runs-on: ubuntu-20.04 + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install stable toolchain + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af + with: + toolchain: nightly + override: true + default: true + - name: Install cargo fuzz + uses: taiki-e/install-action@70233fe3d27d863712ee34eede2087e36bde6b5e + with: + tool: cargo-fuzz + - name: Smoke-test fuzz targets + run: | + cargo fuzz build + for target in $(cargo fuzz list) ; do + cargo fuzz run $target -- -max_total_time=10 + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3b6b6caa0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +**/target +/docs/book +validation/measurement_report.aux +validation/measurement_report.fdb_latexmk +validation/measurement_report.fls +validation/measurement_report.log +validation-ht/measurement_report.aux +validation-ht/measurement_report.fdb_latexmk +validation-ht/measurement_report.fls +validation-ht/measurement_report.log +*swp +**/.idea diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 000000000..44024acab --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,10 @@ +unstable_features = true +format_macro_matchers = true +format_strings = true +hex_literal_case = "Lower" +imports_granularity = "Crate" +normalize_comments = true +normalize_doc_attributes = true +group_imports = "StdExternalCrate" +use_try_shorthand = true +wrap_comments = true diff --git a/404.html b/404.html new file mode 100644 index 000000000..b8b03d16d --- /dev/null +++ b/404.html @@ -0,0 +1,216 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","Is the current timescale the ptp (utc-derived) timescale?","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Put a new measurement in the filter. The filter can then …","","","","","Get the total amount of nanoseconds","Get the total amount of nanoseconds since the origin","Get the total amount of nanoseconds, losing some precision","","","Create a new instance of the filter.","","Create a new sdo id","","Create a Time Properties data set for an Arbitrary …","Create a Time Properties data set for the PTP timescale.","","Get the current time of the clock","Offset to the remote PTP node.","2-log of the variance (in seconds^2) of the clock when not …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Raw offset calculated from a delay message","Raw offset calculated from a sync message","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Get the total amount of seconds","","Get the total amount of seconds","Get the total amount of seconds since the origin","","Set the frequency of the clock, returning the time at …","Adjust the timescale properties of the clock, including …","","","Change the current time of the clock by offset. Returns …","","","","","","Get all the nanoseconds that are under a second","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Update initiated through FilterUpdate::next_update timeout.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[24,0,0,0,0,0,8,0,0,18,15,0,0,24,24,0,0,24,0,23,23,0,0,20,20,20,20,20,20,0,20,20,20,20,20,20,23,24,11,24,20,20,20,20,20,20,0,0,0,0,20,24,24,0,20,24,43,43,43,43,43,0,20,20,20,0,43,43,24,11,24,0,0,0,0,20,20,20,20,20,20,20,24,1,1,5,1,2,1,2,4,5,5,5,11,11,11,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,4,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,22,22,17,17,18,5,20,21,22,23,24,25,26,27,28,1,2,11,17,18,5,20,21,22,23,24,25,26,27,28,1,2,11,21,1,2,11,20,21,22,23,24,25,26,28,1,2,28,5,5,8,30,27,1,1,17,35,7,17,18,5,20,21,22,23,24,25,26,27,28,1,2,11,17,18,5,20,21,22,23,24,25,26,27,28,1,2,11,28,17,18,5,20,21,22,23,24,25,26,26,27,30,28,7,36,6,42,43,35,1,1,2,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,11,1,2,1,11,1,1,2,1,2,1,2,2,1,1,2,7,7,7,7,7,7,7,7,17,18,5,21,26,1,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,35,25,7,25,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,5,8,30,5,1,1,1,2,1,1,1,8,4,26,30,25,25,35,15,28,22,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,21,1,2,11,17,17,28,28,1,1,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,17,1,11,1,2,27,15,15,17,7,15,1,2,2,1,2,2,5,27,17,18,5,20,21,22,23,24,25,26,27,28,1,2,11,26,1,2,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,8,30,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,4,17,18,5,20,21,22,23,24,25,26,27,30,28,7,36,6,42,43,35,1,2,11,58,59,59,60,61,62,63,64,65],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,1],0,[[1,1]],[[2,1]],[[1,1],3],[[2,1],3],[[[4,[-1]],[5,[-2]],-3,-4],[[7,[6,-2,-4,-3,-1]]],8,[],[],9],[[[5,[-1]],-2],10,[],9],0,0,[11,10],[11,1],[11,12],[11,13],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[[[4,[-1]],[14,[[7,[6,-2,-3,-4,-1]]]]],3,8,0,9,15],[[[4,[-1]]],10,8],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],0,0,0,[17,17],[18,18],[[[5,[-1]]],[[5,[-1]]],19],[20,20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[1,1],[2,2],[11,11],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[21,21],29],[[1,1],29],[[2,2],29],[[11,11],29],[[],20],[[],21],[[],22],[[],23],[[],24],[[],25],[[],26],[[],28],[[],1],[[],2],0,0,0,[[-1,-2],3,[],15],[[30,-1],3,15],[[[14,[31]]],[[33,[27,[0,[32]]]]]],[[1,-1],[],34],[[1,-1],3,34],0,[[],35],[[[7,[6,-1,-2,-3,-4]]],[[3,[[7,[36,-1,-2,-3,-4]],35]]],[],[],[],8],[[17,17],37],[[18,18],37],[[[5,[-1]],[5,[-1]]],37,38],[[20,20],37],[[21,21],37],[[22,22],37],[[23,23],37],[[24,24],37],[[25,25],37],[[26,26],37],[[27,27],37],[[28,28],37],[[1,1],37],[[2,2],37],[[11,11],37],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],[[-1,-2],37,[],[]],0,[[17,39],40],[[18,39],40],[[[5,[-1]],39],40,41],[[20,39],40],[[21,39],40],[[22,39],40],[[23,39],40],[[24,39],40],[[25,39],40],[[26,39],40],[[26,39],40],[[27,39],40],[[30,39],40],[[28,39],40],[[[7,[-1,-2,-3,-4,-5]],39],40,41,41,41,41,[41,8]],[[36,39],40],[[6,39],40],[[42,39],40],[[43,39],40],[[35,39],40],[[1,39],40],[[1,39],40],[[2,39],40],[[2,39],40],[[11,39],40],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[13,11],[-1,-1,[]],[-1,1,34],[-1,2,34],[11,1],[13,11],[13,1],[44,1],[45,2],[44,1],[45,2],[44,1],[45,2],[[45,46],2],[12,1],[44,1],[45,2],[[[7,[36,-1,-2,-3,-4]]],35,[],9,15,8],[[[7,[36,-1,-2,-3,-4]]],35,[],9,15,8],[[[7,[36,-1,-2,-3,-4]]],35,[],9,15,8],[[[7,[36,-1,-2,-3,-4]]],35,[],9,15,8],[[[7,[36,-1,-2,-3,-4]],[14,[31]]],35,0,9,15,8],[[[7,[36,-1,-2,-3,-4]],42,2],35,[],9,15,8],[[[7,[36,-1,-2,-3,-4]]],35,[],9,15,8],[[[7,[36,-1,-2,-3,-4]],[14,[31]],2],35,0,9,15,8],[[17,-1],3,47],[[18,-1],3,47],[[[5,[-1]],-2],3,48,47],[[21,-1],3,47],[[26,-1],3,47],[[1,-1],3,47],[[11,-1],3,47],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[25,37],[[[7,[-1,-2,-3,-4,-5]]],37,[],[],[],[],8],[25,23],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,[[16,[-2]]],[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,[[[5,[-1]]],11,[]],[[1,-1],[],34],[[1,-1],3,34],[1,49],[2,50],[1,12],[1,51],[1],[[],-1,[]],[[17,25],[[4,[-1]]],[]],[52,[[16,[26]]]],[12,30],[[37,37,24],25],[[[16,[53]],23,37,37,24],25],[35,16],[-1,2,[]],0,0,[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[-1,[[3,[-2,37]]],[],[]],[[21,21],[[16,[29]]]],[[1,1],[[16,[29]]]],[[2,2],[[16,[29]]]],[[11,11],[[16,[29]]]],0,0,0,0,[[1,1]],[[1,1],3],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[1,12],[11,12],[1,44],[2,45],[[27,[14,[31]]],[[33,[54,[0,[32]]]]]],[[-1,12],[[33,[2]]],[]],[[-1,25],[[33,[3]]],[]],0,[[[7,[36,-1,-2,-3,-4]]],[[7,[6,-1,-2,-3,-4]]],[],9,15,8],[[-1,1],[[33,[2]]],[]],[[1,1]],[[2,1]],[[2,2]],[[1,1],3],[[2,1],3],[2,46],0,[27,[[0,[55]]]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,56,[]],[-1,56,[]],[-1,56,[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,[[33,[-2]]],[],[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,57,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0],"c":[],"p":[[3,"Duration",0],[3,"Time",0],[15,"tuple"],[3,"PtpInstance",0],[3,"PortConfig",0],[3,"InBmca",0],[3,"Port",0],[8,"Filter",0],[8,"Rng",834],[3,"Duration",835],[3,"Interval",0],[15,"f64"],[15,"i8"],[15,"slice"],[8,"Clock",0],[4,"Option",836],[3,"InstanceConfig",0],[4,"DelayMechanism",0],[8,"Clone",837],[4,"ClockAccuracy",0],[3,"ClockIdentity",0],[3,"ClockQuality",0],[4,"LeapIndicator",0],[4,"TimeSource",0],[3,"TimePropertiesDS",0],[3,"SdoId",0],[3,"FuzzMessage",0],[3,"Measurement",0],[4,"Ordering",838],[3,"BasicFilter",0],[15,"u8"],[8,"Error",839],[4,"Result",840],[8,"ToFixed",841],[3,"PortActionIterator",0],[3,"Running",0],[15,"bool"],[8,"PartialEq",838],[3,"Formatter",842],[6,"Result",842],[8,"Debug",842],[3,"TimestampContext",0],[4,"PortAction",0],[15,"i64"],[15,"u64"],[15,"u32"],[8,"Hasher",843],[8,"Hash",843],[6,"I96F32",844],[6,"U96F32",844],[15,"i128"],[15,"u16"],[15,"i16"],[15,"usize"],[8,"Iterator",845],[3,"String",846],[3,"TypeId",847],[13,"E2E",825],[13,"SendTimeCritical",826],[13,"SendGeneral",826],[13,"ResetAnnounceTimer",826],[13,"ResetSyncTimer",826],[13,"ResetDelayRequestTimer",826],[13,"ResetAnnounceReceiptTimer",826],[13,"ResetFilterUpdateTimer",826]]},\
+"statime_linux":{"doc":"","t":"AAASDLLLLLLMLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLDENNNEDNMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLMLLLLLLLLLLLLLLLLLLLLLLMMLLLLMLLLLLLLLMMLLLLLLLLMMMLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLSSSSIFFFFF","n":["clock","config","socket","CLOCK_REALTIME","LinuxClock","az","borrow","borrow_mut","cast_from","checked_as","checked_cast_from","clock","clone","clone_into","error_estimate_update","fmt","from","into","libc_timespec_into_instant","lossless_try_into","lossy_into","now","now","open","overflowing_as","overflowing_cast_from","resolution","saturating_as","saturating_cast_from","set_frequency","set_frequency","set_leap_seconds","set_properties","step_clock","step_clock","to_owned","try_from","try_into","type_id","unwrapped_as","unwrapped_cast_from","vzip","wrapping_as","wrapping_cast_from","Config","ConfigError","Io","Ipv4","Ipv6","NetworkMode","PortConfig","Toml","acceptable_master_list","announce_interval","announce_receipt_timeout","az","az","az","az","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","cast_from","cast_from","cast_from","cast_from","checked_as","checked_as","checked_as","checked_as","checked_cast_from","checked_cast_from","checked_cast_from","checked_cast_from","clone","clone","clone","clone_into","clone_into","clone_into","default","delay_asymetry","delay_mechanism","deserialize","deserialize","deserialize","domain","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from_file","hardware_clock","interface","into","into","into","into","loglevel","lossless_try_into","lossless_try_into","lossless_try_into","lossless_try_into","lossy_into","lossy_into","lossy_into","lossy_into","master_only","network_mode","overflowing_as","overflowing_as","overflowing_as","overflowing_as","overflowing_cast_from","overflowing_cast_from","overflowing_cast_from","overflowing_cast_from","ports","priority1","priority2","saturating_as","saturating_as","saturating_as","saturating_as","saturating_cast_from","saturating_cast_from","saturating_cast_from","saturating_cast_from","sdo_id","sync_interval","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","unwrapped_as","unwrapped_as","unwrapped_as","unwrapped_as","unwrapped_cast_from","unwrapped_cast_from","unwrapped_cast_from","unwrapped_cast_from","vzip","vzip","vzip","vzip","warn_when_unreasonable","wrapping_as","wrapping_as","wrapping_as","wrapping_as","wrapping_cast_from","wrapping_cast_from","wrapping_cast_from","wrapping_cast_from","PDELAY_EVENT","PDELAY_GENERAL","PRIMARY_EVENT","PRIMARY_GENERAL","PtpTargetAddress","open_ipv4_event_socket","open_ipv4_general_socket","open_ipv6_event_socket","open_ipv6_general_socket","timestamp_to_time"],"q":[[0,"statime_linux"],[3,"statime_linux::clock"],[44,"statime_linux::config"],[189,"statime_linux::socket"],[199,"core::option"],[200,"core::time"],[201,"core::result"],[202,"core::fmt"],[203,"core::fmt"],[204,"statime::time::instant"],[205,"clock_steering"],[206,"std::io::error"],[207,"std::path"],[208,"core::convert"],[209,"clock_steering"],[210,"statime::time::duration"],[211,"clock_steering"],[212,"serde::de"],[213,"alloc::string"],[214,"timestamped_socket::interface"],[215,"timestamped_socket::socket"],[216,"core::net::socket_addr"],[217,"timestamped_socket::socket"]],"d":["Implementation of the abstract clock for the linux platform","","Event and General sockets for linux systems","","","","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self)
.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Parse config from file","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Warns about unreasonable config values","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,25,23,23,0,0,25,22,22,22,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,21,22,23,23,22,22,21,22,23,21,21,22,23,21,21,21,22,22,22,23,23,23,21,22,23,25,25,21,22,23,25,21,22,22,21,22,23,25,21,21,22,23,25,21,22,23,25,22,22,21,22,23,25,21,22,23,25,21,21,21,21,22,23,25,21,22,23,25,21,22,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,22,23,25,21,21,22,23,25,21,22,23,25,34,34,34,34,0,0,0,0,0,0],"f":[0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],0,[2,2],[[-1,-2],3,[],[]],[[2,4,4],[[5,[3]]]],[[2,6],7],[-1,-1,[]],[-1,-2,[],[]],[8,9],[-1,[[1,[-2]]],[],[]],[-1,-2,[],[]],[2,9],[2,[[5,[10]]]],[-1,[[11,[2]]],[[13,[12]]]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[2,[[5,[10]]]],[-1,-2,[],[]],[-1,-2,[],[]],[[2,15],[[5,[10]]]],[[2,15],[[5,[9]]]],[[2,16],[[5,[3]]]],[[2,17],[[5,[3]]]],[[2,18],[[5,[9]]]],[[2,19],[[5,[10]]]],[-1,-2,[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,20,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,0,0,0,0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[21,21],[22,22],[23,23],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[-1,-2],3,[],[]],[[],23],0,0,[-1,[[5,[21]]],24],[-1,[[5,[22]]],24],[-1,[[5,[23]]],24],0,[[21,21],14],[[22,22],14],[[23,23],14],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[-1,-2],14,[],[]],[[21,6],7],[[22,6],7],[[23,6],7],[[25,6],7],[[25,6],7],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[-1,-1,[]],[12,[[5,[21,25]]]],0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,[[1,[-2]]],[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],[-1,[[3,[-2,14]]],[],[]],0,0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,26,[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,[[5,[-2]]],[],[]],[-1,20,[]],[-1,20,[]],[-1,20,[]],[-1,20,[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[21,3],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],[-1,-2,[],[]],0,0,0,0,0,[[27,28],[[11,[[31,[29,30]]]]]],[27,[[11,[[31,[29,30]]]]]],[[27,28],[[11,[[31,[32,30]]]]]],[27,[[11,[[31,[32,30]]]]]],[33,9]],"c":[],"p":[[4,"Option",199],[3,"LinuxClock",3],[15,"tuple"],[3,"Duration",200],[4,"Result",201],[3,"Formatter",202],[6,"Result",202],[3,"timespec",203],[3,"Time",204],[3,"Timestamp",205],[6,"Result",206],[3,"Path",207],[8,"AsRef",208],[15,"bool"],[15,"f64"],[4,"LeapIndicator",205],[3,"TimePropertiesDS",209],[3,"Duration",210],[3,"TimeOffset",205],[3,"TypeId",211],[3,"Config",44],[3,"PortConfig",44],[4,"NetworkMode",44],[8,"Deserializer",212],[4,"ConfigError",44],[3,"String",213],[3,"InterfaceName",214],[4,"InterfaceTimestampMode",215],[3,"SocketAddrV4",216],[3,"Open",215],[3,"Socket",215],[3,"SocketAddrV6",216],[3,"Timestamp",215],[8,"PtpTargetAddress",189]]}\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
diff --git a/docs/settings.html b/docs/settings.html
new file mode 100644
index 000000000..eae7ece20
--- /dev/null
+++ b/docs/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +
use crate::ClockIdentity;
+
+pub trait AcceptableMasterList {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool;
+}
+
+impl AcceptableMasterList for () {
+ fn is_acceptable(&self, _identity: ClockIdentity) -> bool {
+ true
+ }
+}
+
+impl AcceptableMasterList for &[ClockIdentity] {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool {
+ self.contains(&identity)
+ }
+}
+
+impl<const CAP: usize> AcceptableMasterList for arrayvec::ArrayVec<ClockIdentity, CAP> {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool {
+ self.contains(&identity)
+ }
+}
+
+#[cfg(feature = "std")]
+impl AcceptableMasterList for std::vec::Vec<ClockIdentity> {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool {
+ self.contains(&identity)
+ }
+}
+
+#[cfg(feature = "std")]
+impl AcceptableMasterList for std::collections::BTreeSet<ClockIdentity> {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool {
+ self.contains(&identity)
+ }
+}
+
+#[cfg(feature = "std")]
+impl AcceptableMasterList for std::collections::HashSet<ClockIdentity> {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool {
+ self.contains(&identity)
+ }
+}
+
+impl<T: AcceptableMasterList> AcceptableMasterList for Option<T> {
+ fn is_acceptable(&self, identity: ClockIdentity) -> bool {
+ match self {
+ Some(list) => list.is_acceptable(identity),
+ None => true,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +
//! Implementation of the best master clock algorithm [Bmca]
+
+use core::cmp::Ordering;
+
+use super::{
+ acceptable_master::AcceptableMasterList,
+ dataset_comparison::{ComparisonDataset, DatasetOrdering},
+ foreign_master::ForeignMasterList,
+};
+use crate::{
+ datastructures::{
+ common::{PortIdentity, TimeInterval},
+ datasets::DefaultDS,
+ messages::{AnnounceMessage, Header},
+ },
+ port::state::PortState,
+ Duration,
+};
+
+/// Object implementing the Best Master Clock Algorithm
+///
+/// Usage:
+///
+/// - Every port has its own instance.
+/// - When a port receives an announce message, it has to register it with the
+/// [Bmca::register_announce_message] method
+/// - When it is time to run the algorithm, the ptp runtime has to take all the
+/// best announce messages using [Bmca::take_best_port_announce_message]
+/// - Of the resulting set, the best global one needs to be determined. This can
+/// be done using [Bmca::find_best_announce_message]
+/// - Then to get the recommended state for each port,
+/// [Bmca::calculate_recommended_state] needs to be called
+#[derive(Debug)]
+pub(crate) struct Bmca<A> {
+ foreign_master_list: ForeignMasterList,
+ acceptable_master_list: A,
+ own_port_identity: PortIdentity,
+}
+
+impl<A> Bmca<A> {
+ pub(crate) fn new(
+ acceptable_master_list: A,
+ own_port_announce_interval: TimeInterval,
+ own_port_identity: PortIdentity,
+ ) -> Self {
+ Self {
+ foreign_master_list: ForeignMasterList::new(
+ own_port_announce_interval,
+ own_port_identity,
+ ),
+ acceptable_master_list,
+ own_port_identity,
+ }
+ }
+
+ pub(crate) fn step_age(&mut self, step: Duration) {
+ self.foreign_master_list.step_age(step);
+ }
+
+ /// Finds the best announce message in the given iterator.
+ /// The port identity in the tuple is the identity of the port that received
+ /// the announce message.
+ pub(crate) fn find_best_announce_message(
+ announce_messages: impl IntoIterator<Item = BestAnnounceMessage>,
+ ) -> Option<BestAnnounceMessage> {
+ announce_messages
+ .into_iter()
+ .max_by(BestAnnounceMessage::compare)
+ }
+
+ fn compare_d0_best(
+ d0: &ComparisonDataset,
+ opt_best: Option<BestAnnounceMessage>,
+ ) -> MessageComparison {
+ match opt_best {
+ None => MessageComparison::Better,
+ Some(best) => {
+ let dataset =
+ ComparisonDataset::from_announce_message(&best.message, &best.identity);
+
+ match d0.compare(&dataset).as_ordering() {
+ Ordering::Less => MessageComparison::Worse(best),
+ Ordering::Equal => MessageComparison::Same,
+ Ordering::Greater => MessageComparison::Better,
+ }
+ }
+ }
+ }
+
+ /// Calculates the recommended port state. This has to be run for every
+ /// port. The PTP spec calls this the State Decision Algorithm.
+ ///
+ /// - `own_data`: Called 'D0' by the PTP spec. The DefaultDS data of our own
+ /// ptp instance.
+ /// - `best_global_announce_message`: Called 'Ebest' by the PTP spec. This
+ /// is the best announce message and the
+ /// identity of the port that received it of all of the best port announce
+ /// messages.
+ /// - `best_port_announce_message`: Called 'Erbest' by the PTP spec. This is
+ /// the best announce message and the
+ /// identity of the port that received it of the port we are calculating the
+ /// recommended state for.
+ /// - `port_state`: The current state of the port we are doing the
+ /// calculation for.
+ ///
+ /// If None is returned, then the port should remain in the same state as it
+ /// is now.
+ pub(crate) fn calculate_recommended_state<F>(
+ own_data: &DefaultDS,
+ best_global_announce_message: Option<BestAnnounceMessage>,
+ best_port_announce_message: Option<BestAnnounceMessage>,
+ port_state: &PortState<F>,
+ ) -> Option<RecommendedState> {
+ if best_global_announce_message.is_none() && matches!(port_state, PortState::Listening) {
+ None
+ } else if (1..=127).contains(&own_data.clock_quality.clock_class) {
+ // only consider the best message of the port
+ Some(Self::calculate_recommended_state_low_class(
+ own_data,
+ best_port_announce_message,
+ ))
+ } else {
+ // see if the best of this port is better than the global best
+ Some(Self::calculate_recommended_state_high_class(
+ own_data,
+ best_global_announce_message,
+ best_port_announce_message,
+ ))
+ }
+ }
+
+ fn calculate_recommended_state_low_class(
+ own_data: &DefaultDS,
+ best_port_announce_message: Option<BestAnnounceMessage>,
+ ) -> RecommendedState {
+ let d0 = ComparisonDataset::from_own_data(own_data);
+
+ match Self::compare_d0_best(&d0, best_port_announce_message) {
+ MessageComparison::Better => RecommendedState::M1(*own_data),
+ MessageComparison::Same => RecommendedState::M1(*own_data),
+ MessageComparison::Worse(port) => RecommendedState::P1(port.message),
+ }
+ }
+
+ fn calculate_recommended_state_high_class(
+ own_data: &DefaultDS,
+ best_global_announce_message: Option<BestAnnounceMessage>,
+ best_port_announce_message: Option<BestAnnounceMessage>,
+ ) -> RecommendedState {
+ let d0 = ComparisonDataset::from_own_data(own_data);
+
+ match Self::compare_d0_best(&d0, best_global_announce_message) {
+ MessageComparison::Better => RecommendedState::M2(*own_data),
+ MessageComparison::Same => RecommendedState::M2(*own_data),
+ MessageComparison::Worse(global_message) => match best_port_announce_message {
+ None => RecommendedState::M3(global_message.message),
+ Some(port_message) => Self::compare_global_and_port(global_message, port_message),
+ },
+ }
+ }
+
+ fn compare_global_and_port(
+ global_message: BestAnnounceMessage,
+ port_message: BestAnnounceMessage,
+ ) -> RecommendedState {
+ if global_message == port_message {
+ // effectively, E_best == E_rbest
+ RecommendedState::S1(global_message.message)
+ } else {
+ let ebest = ComparisonDataset::from_announce_message(
+ &global_message.message,
+ &global_message.identity,
+ );
+
+ let erbest = ComparisonDataset::from_announce_message(
+ &port_message.message,
+ &port_message.identity,
+ );
+
+ // E_best better by topology than E_rbest
+ if matches!(ebest.compare(&erbest), DatasetOrdering::BetterByTopology) {
+ RecommendedState::P2(port_message.message)
+ } else {
+ RecommendedState::M3(global_message.message)
+ }
+ }
+ }
+}
+
+impl<A: AcceptableMasterList> Bmca<A> {
+ /// Register a received announce message to the BMC algorithm
+ pub(crate) fn register_announce_message(
+ &mut self,
+ header: &Header,
+ announce_message: &AnnounceMessage,
+ ) {
+ // Ignore messages comming from the same port
+ if announce_message.header.source_port_identity != self.own_port_identity
+ && self
+ .acceptable_master_list
+ .is_acceptable(announce_message.header.source_port_identity.clock_identity)
+ {
+ self.foreign_master_list.register_announce_message(
+ header,
+ announce_message,
+ Duration::ZERO,
+ );
+ }
+ }
+
+ pub(crate) fn reregister_announce_message(
+ &mut self,
+ header: &Header,
+ announce_message: &AnnounceMessage,
+ age: Duration,
+ ) {
+ // Ignore messages comming from the same port
+ if announce_message.header.source_port_identity != self.own_port_identity
+ && self
+ .acceptable_master_list
+ .is_acceptable(announce_message.header.source_port_identity.clock_identity)
+ {
+ self.foreign_master_list
+ .register_announce_message(header, announce_message, age);
+ }
+ }
+
+ /// Takes the Erbest from this port
+ pub(crate) fn take_best_port_announce_message(&mut self) -> Option<BestAnnounceMessage> {
+ // Find the announce message we want to use from each foreign master that has
+ // qualified messages
+ let announce_messages = self.foreign_master_list.take_qualified_announce_messages();
+
+ // The best of the foreign master messages is our erbest
+ let erbest = Self::find_best_announce_message(announce_messages.map(|message| {
+ BestAnnounceMessage {
+ header: message.header,
+ message: message.message,
+ age: message.age,
+ identity: self.own_port_identity,
+ }
+ }));
+
+ if let Some(best) = &erbest {
+ // All messages that were considered have been removed from the
+ // foreignmasterlist. However, the one that has been selected as the
+ // Erbest must not be removed, so let's just reregister it.
+ self.reregister_announce_message(&best.header, &best.message, best.age);
+ }
+
+ erbest
+ }
+}
+
+#[derive(Debug)]
+enum MessageComparison {
+ Better,
+ Same,
+ Worse(BestAnnounceMessage),
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) struct BestAnnounceMessage {
+ header: Header,
+ message: AnnounceMessage,
+ age: Duration,
+ identity: PortIdentity,
+}
+
+impl BestAnnounceMessage {
+ fn compare(&self, other: &Self) -> Ordering {
+ // use the age as a tie-break if needed (prefer newer messages)
+ let tie_break = other.age.cmp(&self.age);
+ self.compare_dataset(other).as_ordering().then(tie_break)
+ }
+
+ fn compare_dataset(&self, other: &Self) -> DatasetOrdering {
+ let data1 = ComparisonDataset::from_announce_message(&self.message, &self.identity);
+ let data2 = ComparisonDataset::from_announce_message(&other.message, &other.identity);
+
+ data1.compare(&data2)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) enum RecommendedState {
+ M1(DefaultDS),
+ M2(DefaultDS),
+ M3(AnnounceMessage),
+ P1(AnnounceMessage),
+ P2(AnnounceMessage),
+ S1(AnnounceMessage),
+}
+
+#[cfg(test)]
+
+mod tests {
+ use super::*;
+ use crate::{
+ config::InstanceConfig,
+ datastructures::messages::{Header, PtpVersion},
+ ClockIdentity,
+ };
+
+ fn default_announce_message_header() -> Header {
+ Header {
+ sdo_id: Default::default(),
+ version: PtpVersion::new(2, 1).unwrap(),
+ domain_number: Default::default(),
+ alternate_master_flag: false,
+ two_step_flag: false,
+ unicast_flag: false,
+ ptp_profile_specific_1: false,
+ ptp_profile_specific_2: false,
+ leap61: false,
+ leap59: false,
+ current_utc_offset_valid: false,
+ ptp_timescale: false,
+ time_tracable: false,
+ frequency_tracable: false,
+ synchronization_uncertain: false,
+ correction_field: Default::default(),
+ source_port_identity: Default::default(),
+ sequence_id: Default::default(),
+ log_message_interval: Default::default(),
+ }
+ }
+
+ fn default_announce_message() -> AnnounceMessage {
+ AnnounceMessage {
+ header: default_announce_message_header(),
+ origin_timestamp: Default::default(),
+ current_utc_offset: Default::default(),
+ grandmaster_priority_1: Default::default(),
+ grandmaster_clock_quality: Default::default(),
+ grandmaster_priority_2: Default::default(),
+ grandmaster_identity: Default::default(),
+ steps_removed: Default::default(),
+ time_source: Default::default(),
+ }
+ }
+
+ fn default_best_announce_message() -> BestAnnounceMessage {
+ let header = default_announce_message_header();
+ let message = default_announce_message();
+
+ let identity = PortIdentity {
+ clock_identity: ClockIdentity([0; 8]),
+ port_number: 0,
+ };
+
+ BestAnnounceMessage {
+ header,
+ message,
+ age: Duration::ZERO,
+ identity,
+ }
+ }
+
+ #[test]
+ fn test_master_registration() {
+ let mut bmca = Bmca::new((), TimeInterval(100.into()), PortIdentity::default());
+ let mut announce = default_announce_message();
+ announce.header.source_port_identity.clock_identity.0 = [1, 2, 3, 4, 5, 6, 7, 8];
+
+ bmca.register_announce_message(&announce.header, &announce);
+ bmca.register_announce_message(&announce.header, &announce);
+ bmca.register_announce_message(&announce.header, &announce);
+
+ assert!(bmca.take_best_port_announce_message().is_some());
+ assert!(bmca.take_best_port_announce_message().is_some());
+ }
+
+ #[test]
+ fn test_acceptable_master_filter() {
+ let mut bmca = Bmca::new(
+ std::vec![],
+ TimeInterval(100.into()),
+ PortIdentity::default(),
+ );
+ let mut announce = default_announce_message();
+ announce.header.source_port_identity.clock_identity.0 = [1, 2, 3, 4, 5, 6, 7, 8];
+
+ bmca.register_announce_message(&announce.header, &announce);
+ bmca.register_announce_message(&announce.header, &announce);
+ bmca.register_announce_message(&announce.header, &announce);
+
+ assert!(bmca.take_best_port_announce_message().is_none());
+ }
+
+ #[test]
+ fn best_announce_message_compare_equal() {
+ let message1 = default_best_announce_message();
+ let message2 = default_best_announce_message();
+
+ let ordering = message1.compare_dataset(&message2).as_ordering();
+ assert_eq!(ordering, Ordering::Equal);
+ }
+
+ #[test]
+ fn best_announce_message_compare() {
+ let mut message1 = default_best_announce_message();
+ let mut message2 = default_best_announce_message();
+
+ // identities are different
+ message1.message.grandmaster_identity = ClockIdentity([0; 8]);
+ message2.message.grandmaster_identity = ClockIdentity([1; 8]);
+
+ // higher priority is worse in this ordering
+ message1.message.grandmaster_priority_1 = 0;
+ message2.message.grandmaster_priority_1 = 1;
+
+ // hence we expect message1 to be better than message2
+ assert_eq!(message1.compare_dataset(&message2), DatasetOrdering::Better);
+ assert_eq!(message2.compare_dataset(&message1), DatasetOrdering::Worse);
+
+ assert_eq!(message1.compare(&message2), Ordering::Greater);
+ assert_eq!(message2.compare(&message1), Ordering::Less);
+ }
+
+ #[test]
+ fn best_announce_message_max_tie_break() {
+ let mut message1 = default_best_announce_message();
+ let mut message2 = default_best_announce_message();
+
+ message1.age = Duration::from_micros(2);
+ message2.age = Duration::from_micros(1);
+
+ // the newest message should be preferred
+ assert!(message2.age < message1.age);
+
+ let ordering = message1.compare_dataset(&message2).as_ordering();
+ assert_eq!(ordering, Ordering::Equal);
+
+ // so message1 is lower in the ordering than message2
+ assert_eq!(message1.compare(&message2), Ordering::Less)
+ }
+
+ fn default_own_data() -> DefaultDS {
+ let clock_identity = Default::default();
+ let priority_1 = 0;
+ let priority_2 = 0;
+ let domain_number = 0;
+ let slave_only = false;
+ let sdo_id = Default::default();
+
+ DefaultDS::new(InstanceConfig {
+ clock_identity,
+ priority_1,
+ priority_2,
+ domain_number,
+ slave_only,
+ sdo_id,
+ })
+ }
+
+ #[test]
+ fn recommend_state_no_best() {
+ let mut own_data = default_own_data();
+
+ // zero is reserved
+ own_data.clock_quality.clock_class = 1;
+
+ let call = |port_state: &PortState<()>| {
+ Bmca::<()>::calculate_recommended_state(&own_data, None, None, port_state)
+ };
+
+ // when E_best is empty and the port state is listening, it should remain
+ // listening
+ assert!(call(&PortState::Listening).is_none());
+
+ // otherwise it should return a recommendation
+ assert!(matches!(
+ call(&PortState::Passive),
+ Some(RecommendedState::M1(_))
+ ))
+ }
+
+ #[test]
+ fn recommend_state_low_class() {
+ let clock_identity = Default::default();
+ let priority_1 = 0;
+ let priority_2 = 0;
+ let domain_number = 0;
+ let slave_only = false;
+ let sdo_id = Default::default();
+
+ let mut own_data = DefaultDS::new(InstanceConfig {
+ clock_identity,
+ priority_1,
+ priority_2,
+ domain_number,
+ slave_only,
+ sdo_id,
+ });
+
+ own_data.clock_quality.clock_class = 1;
+ assert!((1..=127).contains(&own_data.clock_quality.clock_class));
+
+ // D0 is the same as E_rbest; this is unreachable in practice, but we return M1
+ // in this case
+ let d0 = ComparisonDataset::from_own_data(&own_data);
+ let port_message = default_best_announce_message();
+
+ assert!(matches!(
+ Bmca::<()>::compare_d0_best(&d0, Some(port_message)),
+ MessageComparison::Same
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::M1(own_data)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ None,
+ Some(port_message),
+ &PortState::Passive,
+ )
+ );
+
+ // D0 is the better than E_rbest; M1 is expected
+ let d0 = ComparisonDataset::from_own_data(&own_data);
+ let mut port_message = default_best_announce_message();
+
+ port_message.identity.port_number = 1;
+
+ assert!(matches!(
+ Bmca::<()>::compare_d0_best(&d0, Some(port_message)),
+ MessageComparison::Better
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::M1(own_data)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ None,
+ Some(port_message),
+ &PortState::Passive,
+ )
+ );
+
+ // D0 is NOT better than E_rbest; P1 is expected
+ let mut own_data = own_data;
+
+ let mut port_message = default_best_announce_message();
+
+ own_data.clock_identity = ClockIdentity([0; 8]);
+ port_message.message.grandmaster_identity = ClockIdentity([1; 8]);
+
+ own_data.priority_1 = 1;
+ port_message.message.grandmaster_priority_1 = 0;
+
+ let d0 = ComparisonDataset::from_own_data(&own_data);
+
+ assert!(matches!(
+ Bmca::<()>::compare_d0_best(&d0, Some(port_message)),
+ MessageComparison::Worse(_)
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::P1(port_message.message)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ None,
+ Some(port_message),
+ &PortState::Passive,
+ )
+ );
+ }
+
+ #[test]
+ fn recommend_state_high() {
+ let mut own_data = default_own_data();
+
+ own_data.clock_quality.clock_class = 128;
+ assert!(!(1..=127).contains(&own_data.clock_quality.clock_class));
+
+ // D0 is the same as E_best; this is unreachable in practice, but we return M2
+ // in this case
+ let d0 = ComparisonDataset::from_own_data(&own_data);
+ let global_message = default_best_announce_message();
+
+ assert!(matches!(
+ Bmca::<()>::compare_d0_best(&d0, Some(global_message)),
+ MessageComparison::Same
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::M2(own_data)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ Some(global_message),
+ None,
+ &PortState::Passive,
+ )
+ );
+
+ // D0 is better than E_best; M1 is expected
+ let d0 = ComparisonDataset::from_own_data(&own_data);
+ let mut global_message = default_best_announce_message();
+
+ global_message.identity.port_number = 1;
+
+ assert!(matches!(
+ Bmca::<()>::compare_d0_best(&d0, Some(global_message)),
+ MessageComparison::Better
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::M2(own_data)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ Some(global_message),
+ None,
+ &PortState::Passive,
+ )
+ );
+
+ // D0 is NOT better than E_best
+ let mut own_data = own_data;
+
+ let mut global_message = default_best_announce_message();
+
+ own_data.clock_identity = ClockIdentity([0; 8]);
+ global_message.message.grandmaster_identity = ClockIdentity([1; 8]);
+
+ own_data.priority_1 = 1;
+ global_message.message.grandmaster_priority_1 = 0;
+
+ let d0 = ComparisonDataset::from_own_data(&own_data);
+
+ assert!(matches!(
+ Bmca::<()>::compare_d0_best(&d0, Some(global_message)),
+ MessageComparison::Worse(_)
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::S1(global_message.message)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ Some(global_message),
+ Some(global_message),
+ &PortState::Passive,
+ )
+ );
+ }
+
+ #[test]
+ fn ebest_better_by_topology_no() {
+ let mut own_data = default_own_data();
+ let mut global_message = default_best_announce_message();
+
+ // take the erest branch
+ own_data.clock_quality.clock_class = 128;
+
+ own_data.clock_identity = ClockIdentity([0; 8]);
+ global_message.message.grandmaster_identity = ClockIdentity([1; 8]);
+
+ own_data.priority_1 = 1;
+ global_message.message.grandmaster_priority_1 = 0;
+
+ let mut port_message = global_message;
+
+ global_message.age = Duration::from_micros(4);
+ port_message.age = Duration::from_micros(2);
+
+ let ebest = ComparisonDataset::from_announce_message(
+ &global_message.message,
+ &global_message.identity,
+ );
+
+ let erbest =
+ ComparisonDataset::from_announce_message(&port_message.message, &port_message.identity);
+
+ assert!(!matches!(
+ ebest.compare(&erbest),
+ DatasetOrdering::BetterByTopology
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::M3(global_message.message)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ Some(global_message),
+ Some(port_message),
+ &PortState::Passive,
+ )
+ );
+ }
+
+ #[test]
+ fn ebest_better_by_topology_yes() {
+ let mut own_data = default_own_data();
+ let mut global_message = default_best_announce_message();
+
+ // take the erest branch
+ own_data.clock_quality.clock_class = 128;
+
+ own_data.clock_identity = ClockIdentity([0; 8]);
+ global_message.message.grandmaster_identity = ClockIdentity([1; 8]);
+
+ own_data.priority_1 = 1;
+ global_message.message.grandmaster_priority_1 = 0;
+
+ let mut port_message = global_message;
+
+ global_message.age = Duration::from_micros(4);
+ port_message.age = Duration::from_micros(2);
+
+ let ebest = ComparisonDataset::from_announce_message(
+ &global_message.message,
+ &global_message.identity,
+ );
+
+ let erbest =
+ ComparisonDataset::from_announce_message(&port_message.message, &port_message.identity);
+
+ assert!(!matches!(
+ ebest.compare(&erbest),
+ DatasetOrdering::BetterByTopology
+ ));
+
+ assert_eq!(
+ Some(RecommendedState::M3(global_message.message)),
+ Bmca::<()>::calculate_recommended_state::<()>(
+ &own_data,
+ Some(global_message),
+ Some(port_message),
+ &PortState::Passive,
+ )
+ );
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +
//! Implementation of chapter 9.3.4 Data set comparison algorithm
+
+use core::cmp::Ordering;
+
+use crate::datastructures::{
+ common::{ClockIdentity, ClockQuality, PortIdentity},
+ datasets::DefaultDS,
+ messages::AnnounceMessage,
+};
+
+/// A collection of data that is gathered from other sources (mainly announce
+/// messages and the DefaultDS). When gathered from two different sources, the
+/// [compare](crate::bmc::dataset_comparison::ComparisonDataset) method can be
+/// used to find out which source is better according to the dataset comparison
+/// algorithm.
+#[derive(Eq, PartialEq, Default, Debug)]
+pub(crate) struct ComparisonDataset {
+ gm_priority_1: u8,
+ gm_identity: ClockIdentity,
+ gm_clock_quality: ClockQuality,
+ gm_priority_2: u8,
+ steps_removed: u16,
+ identity_of_senders: ClockIdentity,
+ identity_of_receiver: PortIdentity,
+}
+
+impl ComparisonDataset {
+ /// Create a ComparisonDataset from the data in an announce message and the
+ /// port identity of the port that received the announce message
+ pub(crate) fn from_announce_message(
+ message: &AnnounceMessage,
+ port_receiver_identity: &PortIdentity,
+ ) -> Self {
+ Self {
+ gm_priority_1: message.grandmaster_priority_1,
+ gm_identity: message.grandmaster_identity,
+ gm_clock_quality: message.grandmaster_clock_quality,
+ gm_priority_2: message.grandmaster_priority_2,
+ steps_removed: message.steps_removed,
+ identity_of_senders: message.header.source_port_identity.clock_identity,
+ identity_of_receiver: *port_receiver_identity,
+ }
+ }
+
+ pub(crate) fn from_own_data(data: &DefaultDS) -> Self {
+ Self {
+ gm_priority_1: data.priority_1,
+ gm_identity: data.clock_identity,
+ gm_clock_quality: data.clock_quality,
+ gm_priority_2: data.priority_2,
+ steps_removed: 0,
+ identity_of_senders: data.clock_identity,
+ identity_of_receiver: PortIdentity {
+ clock_identity: data.clock_identity,
+ port_number: 0,
+ },
+ }
+ }
+
+ /// Returns the ordering of `self` in comparison to other.
+ pub(crate) fn compare(&self, other: &Self) -> DatasetOrdering {
+ if self.gm_identity == other.gm_identity {
+ Self::compare_same_identity(self, other)
+ } else {
+ Self::compare_different_identity(self, other)
+ }
+ }
+
+ /// PTP grandmaster instances are different
+ fn compare_different_identity(&self, other: &Self) -> DatasetOrdering {
+ let self_quality = self.gm_clock_quality;
+ let other_quality = other.gm_clock_quality;
+
+ // Figure 34
+ let ordering = (self.gm_priority_1.cmp(&other.gm_priority_1))
+ .then_with(|| self_quality.clock_class.cmp(&other_quality.clock_class))
+ // The spec assumes numerical ordering (which is the reverse of the semantic ordering)
+ .then_with(|| self_quality.clock_accuracy.cmp_numeric(&other_quality.clock_accuracy))
+ .then_with(|| self_quality.offset_scaled_log_variance.cmp(&other_quality.offset_scaled_log_variance))
+ .then_with(|| self.gm_priority_2.cmp(&other.gm_priority_2))
+ .then_with(|| self.gm_identity.cmp(&other.gm_identity));
+
+ match ordering {
+ Ordering::Equal => unreachable!("gm_identity is guaranteed to be different"),
+ Ordering::Greater => DatasetOrdering::Worse,
+ Ordering::Less => DatasetOrdering::Better,
+ }
+ }
+
+ /// Potentially the same PTP grandmaster instance
+ fn compare_same_identity(&self, other: &Self) -> DatasetOrdering {
+ let steps_removed_difference = self.steps_removed as i32 - other.steps_removed as i32;
+
+ // Figure 35
+ match steps_removed_difference {
+ 2..=i32::MAX => DatasetOrdering::Worse,
+ i32::MIN..=-2 => DatasetOrdering::Better,
+ 1 => match Ord::cmp(
+ &self.identity_of_receiver.clock_identity,
+ &self.identity_of_senders,
+ ) {
+ Ordering::Less => DatasetOrdering::Better,
+ Ordering::Equal => DatasetOrdering::Error1,
+ Ordering::Greater => DatasetOrdering::BetterByTopology,
+ },
+ -1 => match Ord::cmp(
+ &other.identity_of_receiver.clock_identity,
+ &other.identity_of_senders,
+ ) {
+ Ordering::Less => DatasetOrdering::Worse,
+ Ordering::Equal => DatasetOrdering::Error1,
+ Ordering::Greater => DatasetOrdering::WorseByTopology,
+ },
+ 0 => {
+ let senders = self.identity_of_senders.cmp(&other.identity_of_senders);
+ let receivers = Ord::cmp(
+ &self.identity_of_receiver.port_number,
+ &other.identity_of_receiver.port_number,
+ );
+
+ match senders.then(receivers) {
+ Ordering::Less => DatasetOrdering::BetterByTopology,
+ Ordering::Equal => DatasetOrdering::Error2,
+ Ordering::Greater => DatasetOrdering::WorseByTopology,
+ }
+ }
+ }
+ }
+}
+
+/// The ordering result of the dataset comparison algorithm
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DatasetOrdering {
+ /// The [ComparisonDataset] is better than the one being compared against
+ Better,
+ /// The [ComparisonDataset] is of equal quality as the one being compared
+ /// against, but is preferred because of the network topology
+ BetterByTopology,
+ /// The [ComparisonDataset] is equal in quality and topology
+ Error1,
+ /// The [ComparisonDataset] is probably based on the same set of data
+ Error2,
+ /// The [ComparisonDataset] is of equal quality as the one being compared
+ /// against, but is not preferred because of the network topology
+ WorseByTopology,
+ /// The [ComparisonDataset] is worse than the one being compared against
+ Worse,
+}
+
+impl DatasetOrdering {
+ pub const fn as_ordering(self) -> Ordering {
+ // We get errors if two announce messages are (functionally) the same
+ // in that case either option is a valid choice
+ match self {
+ DatasetOrdering::Better | DatasetOrdering::BetterByTopology => Ordering::Greater,
+ DatasetOrdering::Error1 | DatasetOrdering::Error2 => Ordering::Equal,
+ DatasetOrdering::WorseByTopology | DatasetOrdering::Worse => Ordering::Less,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::datastructures::common::ClockAccuracy;
+
+ const IDENTITY_A: ClockIdentity = ClockIdentity([1, 1, 1, 1, 1, 1, 1, 1]);
+ const IDENTITY_B: ClockIdentity = ClockIdentity([2, 2, 2, 2, 2, 2, 2, 2]);
+ const IDENTITY_C: ClockIdentity = ClockIdentity([3, 3, 3, 3, 3, 3, 3, 3]);
+
+ fn get_default_test_pair() -> (ComparisonDataset, ComparisonDataset) {
+ Default::default()
+ }
+
+ #[test]
+ fn figure_34() {
+ // Start with two identical datasets
+ let (mut a, mut b) = get_default_test_pair();
+
+ // Now we work bottom up to test everything
+ // Every time we we change which one is better or worse so we know that it's not
+ // still the previous result coming through
+
+ a.gm_identity = IDENTITY_A;
+ b.gm_identity = IDENTITY_B;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Better);
+ assert_eq!(b.compare(&a), DatasetOrdering::Worse);
+
+ a.gm_priority_2 = 1;
+ b.gm_priority_2 = 0;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Worse);
+ assert_eq!(b.compare(&a), DatasetOrdering::Better);
+
+ a.gm_clock_quality.offset_scaled_log_variance = 0;
+ b.gm_clock_quality.offset_scaled_log_variance = 1;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Better);
+ assert_eq!(b.compare(&a), DatasetOrdering::Worse);
+
+ a.gm_clock_quality.clock_accuracy = ClockAccuracy::US1;
+ b.gm_clock_quality.clock_accuracy = ClockAccuracy::NS1;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Worse);
+ assert_eq!(b.compare(&a), DatasetOrdering::Better);
+
+ a.gm_clock_quality.clock_class = 0;
+ b.gm_clock_quality.clock_class = 1;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Better);
+ assert_eq!(b.compare(&a), DatasetOrdering::Worse);
+
+ a.gm_priority_1 = 1;
+ b.gm_priority_1 = 0;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Worse);
+ assert_eq!(b.compare(&a), DatasetOrdering::Better);
+ }
+
+ #[test]
+ fn figure_35() {
+ let (mut a, mut b) = get_default_test_pair();
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Error2);
+ assert_eq!(b.compare(&a), DatasetOrdering::Error2);
+
+ a.identity_of_receiver.port_number = 1;
+ b.identity_of_receiver.port_number = 0;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::WorseByTopology);
+ assert_eq!(b.compare(&a), DatasetOrdering::BetterByTopology);
+
+ a.identity_of_senders = IDENTITY_A;
+ b.identity_of_senders = IDENTITY_B;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::BetterByTopology);
+ assert_eq!(b.compare(&a), DatasetOrdering::WorseByTopology);
+
+ a.steps_removed = 0;
+ a.identity_of_receiver.clock_identity = IDENTITY_A;
+ b.steps_removed = 1;
+ b.identity_of_receiver.clock_identity = IDENTITY_B;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Error1);
+ assert_eq!(b.compare(&a), DatasetOrdering::Error1);
+
+ a.identity_of_receiver.clock_identity = IDENTITY_B;
+ b.identity_of_receiver.clock_identity = IDENTITY_C;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::WorseByTopology);
+ assert_eq!(b.compare(&a), DatasetOrdering::BetterByTopology);
+
+ // the inverse of the identity_of_senders
+ a.identity_of_receiver.clock_identity = IDENTITY_B;
+ b.identity_of_receiver.clock_identity = IDENTITY_A;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Worse);
+ assert_eq!(b.compare(&a), DatasetOrdering::Better);
+
+ a.steps_removed = 0;
+ b.steps_removed = 2;
+
+ assert_eq!(a.compare(&b), DatasetOrdering::Better);
+ assert_eq!(b.compare(&a), DatasetOrdering::Worse);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +
//! Implementation of the [ForeignMasterList]
+
+use arrayvec::ArrayVec;
+
+use crate::{
+ datastructures::{
+ common::{PortIdentity, TimeInterval},
+ messages::{AnnounceMessage, Header},
+ },
+ time::Duration,
+};
+
+/// The time window in which announce messages are valid.
+/// To get the real window, multiply it with the announce interval of the port.
+const FOREIGN_MASTER_TIME_WINDOW: u16 = 4;
+
+/// This is the amount of announce messages that must have been received within
+/// the time window for a foreign master to be valid
+const FOREIGN_MASTER_THRESHOLD: usize = 2;
+
+/// The maximum amount of announce message to store within the time window
+const MAX_ANNOUNCE_MESSAGES: usize = 8;
+
+/// The maximum amount of foreign masters to store at the same time
+const MAX_FOREIGN_MASTERS: usize = 8;
+
+#[derive(Debug)]
+pub struct ForeignMaster {
+ foreign_master_port_identity: PortIdentity,
+ // Must have a capacity of at least 2
+ announce_messages: ArrayVec<ForeignAnnounceMessage, MAX_ANNOUNCE_MESSAGES>,
+}
+
+#[derive(Debug)]
+pub(crate) struct ForeignAnnounceMessage {
+ pub(crate) header: Header,
+ pub(crate) message: AnnounceMessage,
+ pub(crate) age: Duration,
+}
+
+impl ForeignMaster {
+ fn new(header: Header, announce_message: AnnounceMessage) -> Self {
+ let message = ForeignAnnounceMessage {
+ header,
+ message: announce_message,
+ age: Duration::ZERO,
+ };
+
+ let mut messages = ArrayVec::<_, MAX_ANNOUNCE_MESSAGES>::new();
+ messages.push(message);
+
+ Self {
+ foreign_master_port_identity: announce_message.header.source_port_identity,
+ announce_messages: messages,
+ }
+ }
+
+ fn foreign_master_port_identity(&self) -> PortIdentity {
+ self.foreign_master_port_identity
+ }
+
+ /// Removes all messages that fall outside of the
+ /// [FOREIGN_MASTER_TIME_WINDOW].
+ ///
+ /// Returns true if this foreign master has no more announce messages left.
+ fn purge_old_messages(&mut self, announce_interval: TimeInterval) -> bool {
+ let cutoff_age = Duration::from(announce_interval) * FOREIGN_MASTER_TIME_WINDOW;
+ self.announce_messages.retain(|m| m.age < cutoff_age);
+
+ self.announce_messages.is_empty()
+ }
+
+ fn register_announce_message(
+ &mut self,
+ header: Header,
+ announce_message: AnnounceMessage,
+ announce_interval: TimeInterval,
+ age: Duration,
+ ) {
+ self.purge_old_messages(announce_interval);
+
+ let new_message = ForeignAnnounceMessage {
+ header,
+ message: announce_message,
+ age,
+ };
+
+ // Try to add new message; otherwise remove the first message and then add
+ if let Err(e) = self.announce_messages.try_push(new_message) {
+ self.announce_messages.remove(0);
+ self.announce_messages.push(e.element());
+ }
+ }
+
+ fn step_age(&mut self, step: Duration, announce_interval: TimeInterval) -> bool {
+ for message in &mut self.announce_messages {
+ message.age += step;
+ }
+
+ self.purge_old_messages(announce_interval)
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct ForeignMasterList {
+ // Must have a capacity of at least 5
+ foreign_masters: ArrayVec<ForeignMaster, MAX_FOREIGN_MASTERS>,
+ own_port_announce_interval: TimeInterval,
+ own_port_identity: PortIdentity,
+}
+
+impl ForeignMasterList {
+ /// - `port_announce_interval`: The time interval derived from the
+ /// PortDS.log_announce_interval
+ /// - `port_identity`: The identity of the port for which this list is used
+ pub(crate) fn new(
+ own_port_announce_interval: TimeInterval,
+ own_port_identity: PortIdentity,
+ ) -> Self {
+ Self {
+ foreign_masters: ArrayVec::<ForeignMaster, MAX_FOREIGN_MASTERS>::new(),
+ own_port_announce_interval,
+ own_port_identity,
+ }
+ }
+
+ pub(crate) fn step_age(&mut self, step: Duration) {
+ for i in (0..self.foreign_masters.len()).rev() {
+ // Purge the old timestamps so we can check the FOREIGN_MASTER_THRESHOLD
+ if self.foreign_masters[i].step_age(step, self.own_port_announce_interval) {
+ // There are no announce messages left, so let's remove this foreign master
+ self.foreign_masters.remove(i);
+ continue;
+ }
+ }
+ }
+
+ /// Takes the qualified announce message of all foreign masters that have
+ /// one
+ pub(crate) fn take_qualified_announce_messages(
+ &mut self,
+ ) -> impl Iterator<Item = ForeignAnnounceMessage> {
+ let mut qualified_foreign_masters = ArrayVec::<_, MAX_FOREIGN_MASTERS>::new();
+
+ for i in (0..self.foreign_masters.len()).rev() {
+ // A foreign master must have at least FOREIGN_MASTER_THRESHOLD messages in the
+ // last FOREIGN_MASTER_TIME_WINDOW to be qualified, so we filter out
+ // any that don't have that
+ if self.foreign_masters[i].announce_messages.len() > FOREIGN_MASTER_THRESHOLD {
+ // Only the most recent announce message is qualified, so we remove that one
+ // from the list
+ let last_index = self.foreign_masters[i].announce_messages.len() - 1;
+ qualified_foreign_masters
+ .push(self.foreign_masters[i].announce_messages.remove(last_index));
+ continue;
+ }
+ }
+
+ qualified_foreign_masters.into_iter()
+ }
+
+ pub(crate) fn register_announce_message(
+ &mut self,
+ header: &Header,
+ announce_message: &AnnounceMessage,
+ age: Duration,
+ ) {
+ if !self.is_announce_message_qualified(announce_message) {
+ // We don't want to store unqualified messages
+ return;
+ }
+
+ let port_announce_interval = self.own_port_announce_interval;
+
+ // Is the foreign master that the message represents already known?
+ if let Some(foreign_master) =
+ self.get_foreign_master_mut(announce_message.header.source_port_identity)
+ {
+ // Yes, so add the announce message to it
+ foreign_master.register_announce_message(
+ *header,
+ *announce_message,
+ port_announce_interval,
+ age,
+ );
+ } else {
+ // No, insert a new foreign master, if there is room in the array
+ if self.foreign_masters.len() < MAX_FOREIGN_MASTERS {
+ self.foreign_masters
+ .push(ForeignMaster::new(*header, *announce_message));
+ }
+ }
+ }
+
+ fn get_foreign_master_mut(
+ &mut self,
+ port_identity: PortIdentity,
+ ) -> Option<&mut ForeignMaster> {
+ self.foreign_masters
+ .iter_mut()
+ .find(|fm| fm.foreign_master_port_identity() == port_identity)
+ }
+
+ fn get_foreign_master(&self, port_identity: PortIdentity) -> Option<&ForeignMaster> {
+ self.foreign_masters
+ .iter()
+ .find(|fm| fm.foreign_master_port_identity() == port_identity)
+ }
+
+ fn is_announce_message_qualified(&self, announce_message: &AnnounceMessage) -> bool {
+ let source_identity = announce_message.header.source_port_identity;
+
+ // 1. The message must not come from our own ptp instance. Since every instance
+ // only has 1 clock, we can check the clock identity. That must be
+ // different.
+ if source_identity.clock_identity == self.own_port_identity.clock_identity {
+ return false;
+ }
+
+ // 2. The announce message must be newer than the one(s) we already have
+ // We can check the sequence id for that (with some logic for u16 rollover)
+ if let Some(foreign_master) = self.get_foreign_master(source_identity) {
+ if let Some(last_announce_message) = foreign_master.announce_messages.last() {
+ let announce_sequence_id = announce_message.header.sequence_id;
+ let last_sequence_id = last_announce_message.header.sequence_id;
+
+ if last_sequence_id >= FOREIGN_MASTER_TIME_WINDOW {
+ if announce_sequence_id < last_sequence_id {
+ return false;
+ }
+ } else if announce_sequence_id - last_sequence_id
+ > u16::MAX - FOREIGN_MASTER_TIME_WINDOW
+ {
+ return false;
+ }
+ }
+ }
+
+ // 3. The announce message must not have a steps removed of 255 and greater
+ if announce_message.steps_removed >= 255 {
+ return false;
+ }
+
+ // 4. The announce message may not be from a foreign master with fewer messages
+ // than FOREIGN_MASTER_THRESHOLD, but that is handled in the
+ // `take_qualified_announce_messages` method.
+
+ // Otherwise, the announce message is qualified
+ true
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +
//! Definitions and implementations of the abstract clock types
+
+use crate::{
+ datastructures::datasets::TimePropertiesDS,
+ time::{Duration, Time},
+};
+
+/// Clock manipulation and querying interface
+///
+/// The clock trait is the primary way the PTP stack interfaces with the
+/// system's clock. It's implementation should be provided by the user of the
+/// Statime crate, and should provide information on and ways to manipulate the
+/// system's clock. An implementation of this trait for linux is provided in the
+/// statime-linux crate.
+///
+/// Note that the clock implementation is responsible for handling leap seconds.
+/// On most operating systems, this will be provided for by the OS, but on some
+/// platforms this may require extra logic.
+pub trait Clock {
+ type Error: core::fmt::Debug;
+
+ /// Get the current time of the clock
+ fn now(&self) -> Time;
+
+ /// Change the current time of the clock by offset. Returns
+ /// the time at which the change was applied.
+ ///
+ /// The applied correction should be as close as possible to
+ /// the requested correction. The reported time of the change
+ /// should be as close as possible to the time the change was
+ /// applied
+ fn step_clock(&mut self, offset: Duration) -> Result<Time, Self::Error>;
+
+ /// Set the frequency of the clock, returning the time
+ /// at which the change was applied. The value is in ppm
+ /// difference from the clocks base frequency.
+ ///
+ /// The applied correction should be as close as possible to
+ /// the requested correction. The reported time of the change
+ /// should be as close as possible to the time the change was
+ /// applied
+ fn set_frequency(&mut self, ppm: f64) -> Result<Time, Self::Error>;
+
+ /// Adjust the timescale properties of the clock, including
+ /// things like the leap indicator, to the extend supported by the
+ /// system.
+ fn set_properties(&mut self, time_properties_ds: &TimePropertiesDS) -> Result<(), Self::Error>;
+}
+
use crate::{ClockIdentity, SdoId};
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct InstanceConfig {
+ pub clock_identity: ClockIdentity,
+ pub priority_1: u8,
+ pub priority_2: u8,
+ pub domain_number: u8,
+ pub slave_only: bool,
+ pub sdo_id: SdoId,
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +
use rand::Rng;
+
+use crate::{time::Interval, Duration};
+
+/// Which delay mechanism a port is using.
+///
+/// Currently, statime only supports the end to end (E2E) delay mechanism.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub enum DelayMechanism {
+ /// End to end delay mechanism. Delay measurement is done directly to the
+ /// chosen master, across potential transparent nodes in between.
+ ///
+ /// the interval corresponds to the PortDS logMinDelayReqInterval
+ E2E { interval: Interval },
+ // No support for other delay mechanisms
+}
+
+/// Configuration items of the PTP PortDS dataset. Dynamical fields are kept
+/// as part of [crate::port::Port].
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct PortConfig<A> {
+ pub acceptable_master_list: A,
+ pub delay_mechanism: DelayMechanism,
+ pub announce_interval: Interval,
+ // more like announce_message_retries. Specifies how many announce_intervals to wait until the
+ // announce message expires.
+ pub announce_receipt_timeout: u8,
+ pub sync_interval: Interval,
+ pub master_only: bool,
+ pub delay_asymmetry: Duration,
+ // Notes:
+ // Fields specific for delay mechanism are kept as part of [DelayMechanism].
+ // Version is always 2.1, so not stored (versionNumber, minorVersionNumber)
+}
+
+impl<A> PortConfig<A> {
+ pub fn min_delay_req_interval(&self) -> Interval {
+ match self.delay_mechanism {
+ DelayMechanism::E2E { interval } => interval,
+ }
+ }
+
+ // section 9.2.6.12
+ pub fn announce_duration(&self, rng: &mut impl Rng) -> core::time::Duration {
+ // add some randomness so that not all timers expire at the same time
+ let factor = 1.0 + rng.sample::<f64, _>(rand::distributions::Open01);
+ let duration = self.announce_interval.as_core_duration();
+
+ duration.mul_f64(factor * self.announce_receipt_timeout as u32 as f64)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +
use core::cmp::Ordering;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// How accurate the underlying clock device is expected to be when not
+/// synchronized.
+pub enum ClockAccuracy {
+ /// Reserved
+ Reserved,
+ /// Accurate within 1 ps
+ PS1,
+ /// Accurate within 2.5 ps
+ PS2_5,
+ /// Accurate within 10 ps
+ PS10,
+ /// Accurate within 25 ps
+ PS25,
+ /// Accurate within 100 ps
+ PS100,
+ /// Accurate within 250 ps
+ PS250,
+ /// Accurate within 1 ns
+ NS1,
+ /// Accurate within 2.5 ns
+ NS2_5,
+ /// Accurate within 10 ns
+ NS10,
+ /// Accurate within 25 ns
+ NS25,
+ /// Accurate within 100 ns
+ NS100,
+ /// Accurate within 250 ns
+ NS250,
+ /// Accurate within 1 us
+ US1,
+ /// Accurate within 2.5 us
+ US2_5,
+ /// Accurate within 10 us
+ US10,
+ /// Accurate within 25 us
+ US25,
+ /// Accurate within 100 us
+ US100,
+ /// Accurate within 250 us
+ US250,
+ /// Accurate within 1 ms
+ MS1,
+ /// Accurate within 2.5 ms
+ MS2_5,
+ /// Accurate within 10 ms
+ MS10,
+ /// Accurate within 25 ms
+ MS25,
+ /// Accurate within 100 ms
+ MS100,
+ /// Accurate within 250 ms
+ MS250,
+ /// Accurate within 1 s
+ S1,
+ /// Accurate within 10 s
+ S10,
+ /// Accurate within >10 s
+ SGT10,
+ /// Specific to a profile
+ ProfileSpecific(u8),
+ /// Accuracy is unknown
+ Unknown,
+}
+
+impl ClockAccuracy {
+ pub(crate) fn to_primitive(self) -> u8 {
+ match self {
+ Self::Reserved => 0x00,
+ Self::PS1 => 0x17,
+ Self::PS2_5 => 0x18,
+ Self::PS10 => 0x19,
+ Self::PS25 => 0x1a,
+ Self::PS100 => 0x1b,
+ Self::PS250 => 0x1c,
+ Self::NS1 => 0x1d,
+ Self::NS2_5 => 0x1e,
+ Self::NS10 => 0x1f,
+ Self::NS25 => 0x20,
+ Self::NS100 => 0x21,
+ Self::NS250 => 0x22,
+ Self::US1 => 0x23,
+ Self::US2_5 => 0x24,
+ Self::US10 => 0x25,
+ Self::US25 => 0x26,
+ Self::US100 => 0x27,
+ Self::US250 => 0x28,
+ Self::MS1 => 0x29,
+ Self::MS2_5 => 0x2a,
+ Self::MS10 => 0x2b,
+ Self::MS25 => 0x2c,
+ Self::MS100 => 0x2d,
+ Self::MS250 => 0x2e,
+ Self::S1 => 0x2f,
+ Self::S10 => 0x30,
+ Self::SGT10 => 0x31,
+ Self::ProfileSpecific(value) => 0x80 + value,
+ Self::Unknown => 0xfe,
+ }
+ }
+
+ pub(crate) fn from_primitive(value: u8) -> Self {
+ match value {
+ 0x00..=0x16 | 0x32..=0x7f | 0xff => Self::Reserved,
+ 0x17 => Self::PS1,
+ 0x18 => Self::PS2_5,
+ 0x19 => Self::PS10,
+ 0x1a => Self::PS25,
+ 0x1b => Self::PS100,
+ 0x1c => Self::PS250,
+ 0x1d => Self::NS1,
+ 0x1e => Self::NS2_5,
+ 0x1f => Self::NS10,
+ 0x20 => Self::NS25,
+ 0x21 => Self::NS100,
+ 0x22 => Self::NS250,
+ 0x23 => Self::US1,
+ 0x24 => Self::US2_5,
+ 0x25 => Self::US10,
+ 0x26 => Self::US25,
+ 0x27 => Self::US100,
+ 0x28 => Self::US250,
+ 0x29 => Self::MS1,
+ 0x2a => Self::MS2_5,
+ 0x2b => Self::MS10,
+ 0x2c => Self::MS25,
+ 0x2d => Self::MS100,
+ 0x2e => Self::MS250,
+ 0x2f => Self::S1,
+ 0x30 => Self::S10,
+ 0x31 => Self::SGT10,
+ 0x80..=0xfd => Self::ProfileSpecific(value - 0x80),
+ 0xfe => ClockAccuracy::Unknown,
+ }
+ }
+
+ /// high accuracy to low accuracy
+ pub(crate) fn cmp_numeric(&self, other: &Self) -> Ordering {
+ self.to_primitive().cmp(&other.to_primitive())
+ }
+}
+
+impl Default for ClockAccuracy {
+ fn default() -> Self {
+ Self::Unknown
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn network_protocol_values() {
+ for i in 0..u8::MAX {
+ let protocol = ClockAccuracy::from_primitive(i);
+ if !matches!(protocol, ClockAccuracy::Reserved) {
+ assert_eq!(protocol.to_primitive(), i);
+ }
+ }
+
+ assert_eq!(ClockAccuracy::ProfileSpecific(5).to_primitive(), 0x85);
+ }
+
+ #[test]
+ fn ordering() {
+ // the inaccuracy of PS1 is less than of PS10
+ let a = ClockAccuracy::PS1;
+ let b = ClockAccuracy::PS10;
+
+ assert_eq!(a.cmp_numeric(&b), Ordering::Less);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +
use crate::datastructures::{WireFormat, WireFormatError};
+
+/// The identity of a PTP node.
+///
+/// Must have a unique value for each node in a ptp network. For notes on
+/// generating these, see IEEE1588-2019 section 7.5.2.2
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
+pub struct ClockIdentity(pub [u8; 8]);
+
+impl WireFormat for ClockIdentity {
+ fn wire_size(&self) -> usize {
+ 8
+ }
+
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ buffer[0..8].copy_from_slice(&self.0);
+ Ok(())
+ }
+
+ fn deserialize(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ Ok(Self(buffer[0..8].try_into().unwrap()))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08u8],
+ ClockIdentity([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 8];
+ object_representation
+ .serialize(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = ClockIdentity::deserialize(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +
use super::clock_accuracy::ClockAccuracy;
+use crate::datastructures::{WireFormat, WireFormatError};
+
+/// A description of the accuracy and type of a clock.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct ClockQuality {
+ /// The PTP clock class.
+ ///
+ /// Per the standard, 248 is the default, and a good option for most use
+ /// cases. For grandmaster clocks, this should be below 128 to ensure the
+ /// clock never takes time from another source. A value of 6 is a good
+ /// option for a node with an external time source.
+ ///
+ /// For other potential values, see IEEE1588-2019 section 7.6.2.5
+ pub clock_class: u8,
+
+ /// The accuracy of the clock
+ pub clock_accuracy: ClockAccuracy,
+
+ /// 2-log of the variance (in seconds^2) of the clock when not synchronized.
+ /// See IEEE1588-2019 section 7.6.3.5 for more details.
+ pub offset_scaled_log_variance: u16,
+}
+
+impl WireFormat for ClockQuality {
+ fn wire_size(&self) -> usize {
+ 4
+ }
+
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ buffer[0] = self.clock_class;
+ buffer[1] = self.clock_accuracy.to_primitive();
+ buffer[2..4].copy_from_slice(&self.offset_scaled_log_variance.to_be_bytes());
+ Ok(())
+ }
+
+ fn deserialize(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ Ok(Self {
+ clock_class: buffer[0],
+ clock_accuracy: ClockAccuracy::from_primitive(buffer[1]),
+ offset_scaled_log_variance: u16::from_be_bytes(buffer[2..4].try_into().unwrap()),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [0x7a, 0x2a, 0x12, 0x34u8],
+ ClockQuality {
+ clock_class: 122,
+ clock_accuracy: ClockAccuracy::MS2_5,
+ offset_scaled_log_variance: 0x1234,
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 4];
+ object_representation
+ .serialize(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = ClockQuality::deserialize(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
//! Common data structures that are used throughout the protocol
+
+mod clock_accuracy;
+mod clock_identity;
+mod clock_quality;
+mod leap_indicator;
+mod port_identity;
+mod time_interval;
+mod time_source;
+mod timestamp;
+mod tlv;
+
+pub use clock_accuracy::*;
+pub use clock_identity::*;
+pub use clock_quality::*;
+pub use leap_indicator::*;
+pub(crate) use port_identity::*;
+pub(crate) use time_interval::*;
+pub use time_source::*;
+pub use timestamp::*;
+pub use tlv::*;
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +
use super::clock_identity::ClockIdentity;
+use crate::datastructures::{WireFormat, WireFormatError};
+
+/// Identity of a single port of a PTP instance
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)]
+pub(crate) struct PortIdentity {
+ /// Identity of the clock this port is part of
+ pub(crate) clock_identity: ClockIdentity,
+ /// Index of the port (1-based).
+ pub(crate) port_number: u16,
+}
+
+impl WireFormat for PortIdentity {
+ fn wire_size(&self) -> usize {
+ 10
+ }
+
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ self.clock_identity.serialize(&mut buffer[0..8])?;
+ buffer[8..10].copy_from_slice(&self.port_number.to_be_bytes());
+ Ok(())
+ }
+
+ fn deserialize(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ Ok(Self {
+ clock_identity: ClockIdentity::deserialize(&buffer[0..8])?,
+ port_number: u16::from_be_bytes(buffer[8..10].try_into().unwrap()),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [
+ (
+ [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x15, 0xb3u8],
+ PortIdentity {
+ clock_identity: ClockIdentity([0, 1, 2, 3, 4, 5, 6, 7]),
+ port_number: 5555,
+ },
+ ),
+ (
+ [0x40, 0x6d, 0x16, 0x36, 0xc4, 0x24, 0x0e, 0x38, 0x04, 0xd2u8],
+ PortIdentity {
+ clock_identity: ClockIdentity([64, 109, 22, 54, 196, 36, 14, 56]),
+ port_number: 1234,
+ },
+ ),
+ ];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 10];
+ object_representation
+ .serialize(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = PortIdentity::deserialize(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +
use core::ops::{Deref, DerefMut};
+
+use fixed::types::I48F16;
+
+use crate::{
+ datastructures::{WireFormat, WireFormatError},
+ time::Duration,
+};
+
+/// Represents time intervals in nanoseconds
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub(crate) struct TimeInterval(pub I48F16);
+
+impl Deref for TimeInterval {
+ type Target = I48F16;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for TimeInterval {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl WireFormat for TimeInterval {
+ fn wire_size(&self) -> usize {
+ 8
+ }
+
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ buffer[0..8].copy_from_slice(&self.0.to_bits().to_be_bytes());
+ Ok(())
+ }
+
+ fn deserialize(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ Ok(Self(I48F16::from_bits(i64::from_be_bytes(
+ buffer[0..8].try_into().unwrap(),
+ ))))
+ }
+}
+
+impl From<Duration> for TimeInterval {
+ fn from(duration: Duration) -> Self {
+ let val = (duration.nanos().to_bits() >> 16) as i64;
+ TimeInterval(fixed::types::I48F16::from_bits(val))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn time_interval_wireformat() {
+ let representations = [
+ (
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00u8],
+ TimeInterval(I48F16::from_num(2.5f64)),
+ ),
+ (
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01u8],
+ TimeInterval(I48F16::from_num(1.0f64 / u16::MAX as f64)),
+ ),
+ (
+ [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00u8],
+ TimeInterval(I48F16::from_num(-1.0f64)),
+ ),
+ ];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 8];
+ object_representation
+ .serialize(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = TimeInterval::deserialize(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +
/// What the time values for a system are derived from
+///
+/// This enum encodes the root source of a system's time values. For most use
+/// cases, the default `InternalOscillator` will suffice.
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TimeSource {
+ AtomicClock,
+ Gnss,
+ TerrestrialRadio,
+ SerialTimeCode,
+ Ptp,
+ Ntp,
+ HandSet,
+ Other,
+ #[default]
+ InternalOscillator,
+ ProfileSpecific(u8),
+ Reserved,
+ /// Time source is unknown. This is not an official variant from the spec,
+ /// but we just need it in practise
+ Unknown(u8),
+}
+
+impl TimeSource {
+ pub(crate) fn to_primitive(self) -> u8 {
+ match self {
+ Self::AtomicClock => 0x10,
+ Self::Gnss => 0x20,
+ Self::TerrestrialRadio => 0x30,
+ Self::SerialTimeCode => 0x39,
+ Self::Ptp => 0x40,
+ Self::Ntp => 0x50,
+ Self::HandSet => 0x60,
+ Self::Other => 0x90,
+ Self::InternalOscillator => 0xa0,
+ Self::ProfileSpecific(p) => 0xf0 + p,
+ Self::Reserved => 0xff,
+ Self::Unknown(v) => v,
+ }
+ }
+
+ pub(crate) fn from_primitive(value: u8) -> Self {
+ match value {
+ 0x10 => Self::AtomicClock,
+ 0x20 => Self::Gnss,
+ 0x30 => Self::TerrestrialRadio,
+ 0x39 => Self::SerialTimeCode,
+ 0x40 => Self::Ptp,
+ 0x50 => Self::Ntp,
+ 0x60 => Self::HandSet,
+ 0x90 => Self::Other,
+ 0xa0 => Self::InternalOscillator,
+ 0xf0..=0xfe => Self::ProfileSpecific(value - 0xf0),
+ 0xff => TimeSource::Reserved,
+ v => TimeSource::Unknown(v),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn network_protocol_values() {
+ for i in 0..u8::MAX {
+ let protocol = TimeSource::from_primitive(i);
+ assert_eq!(protocol.to_primitive(), i);
+ }
+
+ assert_eq!(TimeSource::ProfileSpecific(5).to_primitive(), 0xf5);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +
use crate::{
+ datastructures::{WireFormat, WireFormatError},
+ time::Time,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)]
+pub struct WireTimestamp {
+ /// The seconds field of the timestamp.
+ /// 48-bit, must be less than 281474976710656
+ pub seconds: u64,
+ /// The nanoseconds field of the timestamp.
+ /// Must be less than 10^9
+ pub nanos: u32,
+}
+
+impl WireFormat for WireTimestamp {
+ fn wire_size(&self) -> usize {
+ 10
+ }
+
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ buffer[0..6].copy_from_slice(&self.seconds.to_be_bytes()[2..8]);
+ buffer[6..10].copy_from_slice(&self.nanos.to_be_bytes());
+ Ok(())
+ }
+
+ fn deserialize(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ let mut seconds_buffer = [0; 8];
+ seconds_buffer[2..8].copy_from_slice(&buffer[0..6]);
+
+ Ok(Self {
+ seconds: u64::from_be_bytes(seconds_buffer),
+ nanos: u32::from_be_bytes(buffer[6..10].try_into().unwrap()),
+ })
+ }
+}
+
+impl From<Time> for WireTimestamp {
+ fn from(instant: Time) -> Self {
+ WireTimestamp {
+ seconds: instant.secs(),
+ nanos: instant.subsec_nanos(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [
+ (
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01u8],
+ WireTimestamp {
+ seconds: 0x0000_0000_0002,
+ nanos: 0x0000_0001,
+ },
+ ),
+ (
+ [0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x01u8],
+ WireTimestamp {
+ seconds: 0x1000_0000_0002,
+ nanos: 0x1000_0001,
+ },
+ ),
+ ];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 10];
+ object_representation
+ .serialize(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = WireTimestamp::deserialize(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +
use crate::datastructures::WireFormatError;
+
+#[derive(Clone, PartialEq, Eq, Default)]
+pub(crate) struct TlvSet<'a> {
+ bytes: &'a [u8],
+}
+
+impl<'a> core::fmt::Debug for TlvSet<'a> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("TlvSet")
+ .field("wire_size", &self.wire_size())
+ .field("bytes", &self.bytes)
+ .finish()
+ }
+}
+
+impl<'a> TlvSet<'a> {
+ pub(crate) fn wire_size(&self) -> usize {
+ // tlv should be an even number of octets!
+ debug_assert_eq!(self.bytes.len() % 2, 0);
+
+ self.bytes.len()
+ }
+
+ pub(crate) fn serialize(&self, buffer: &mut [u8]) -> Result<usize, WireFormatError> {
+ buffer
+ .get_mut(..self.bytes.len())
+ .ok_or(WireFormatError::BufferTooShort)?
+ .copy_from_slice(self.bytes);
+
+ Ok(self.bytes.len())
+ }
+
+ pub(crate) fn deserialize(mut buffer: &'a [u8]) -> Result<Self, WireFormatError> {
+ let original = buffer;
+ let mut total_length = 0;
+
+ while buffer.len() > 4 {
+ let _tlv_type = TlvType::from_primitive(u16::from_be_bytes([buffer[0], buffer[1]]));
+ let length = u16::from_be_bytes([buffer[2], buffer[3]]) as usize;
+
+ if length % 2 != 0 {
+ log::trace!("tlv list has trailing bytes");
+ return Err(WireFormatError::Invalid);
+ }
+
+ buffer = buffer
+ .get(4 + length..)
+ .ok_or(WireFormatError::BufferTooShort)?;
+
+ total_length += 4 + length;
+ }
+
+ if !buffer.is_empty() {
+ log::trace!("tlv list has trailing bytes");
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ Ok(Self {
+ bytes: &original[..total_length],
+ })
+ }
+
+ #[allow(unused)]
+ pub fn announce_propagate_tlv(&self) -> impl Iterator<Item = Tlv<'a>> + 'a {
+ self.tlv().filter(|tlv| tlv.tlv_type.announce_propagate())
+ }
+
+ pub(crate) fn tlv(&self) -> impl Iterator<Item = Tlv<'a>> + 'a {
+ let mut buffer = self.bytes;
+
+ core::iter::from_fn(move || {
+ if buffer.len() <= 4 {
+ debug_assert_eq!(buffer.len(), 0);
+ return None;
+ }
+
+ // we've validated the buffer; this should never fail!
+ let tlv = Tlv::deserialize(buffer).unwrap();
+
+ buffer = &buffer[tlv.wire_size()..];
+
+ Some(tlv)
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Tlv<'a> {
+ pub tlv_type: TlvType,
+ pub value: &'a [u8],
+}
+
+impl<'a> Tlv<'a> {
+ fn wire_size(&self) -> usize {
+ 4 + self.value.len()
+ }
+
+ #[allow(unused)]
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ buffer[0..][..2].copy_from_slice(&self.tlv_type.to_primitive().to_be_bytes());
+ buffer[2..][..2].copy_from_slice(&(self.value.len() as u16).to_be_bytes());
+ buffer[4..][..self.value.len()].copy_from_slice(self.value);
+
+ Ok(())
+ }
+
+ fn deserialize(buffer: &'a [u8]) -> Result<Self, WireFormatError> {
+ if buffer.len() < 4 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ let tlv_type = TlvType::from_primitive(u16::from_be_bytes([buffer[0], buffer[1]]));
+ let length = u16::from_be_bytes([buffer[2], buffer[3]]);
+
+ // Parse TLV content / value
+ if buffer.len() < 4 + length as usize {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ let value = &buffer[4..][..length as usize];
+ Ok(Self { tlv_type, value })
+ }
+}
+
+/// See 14.1.1 / Table 52
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum TlvType {
+ Reserved(u16),
+ Management,
+ ManagementErrorStatus,
+ OrganizationExtension,
+ RequestUnicastTransmission,
+ GrantUnicastTransmission,
+ CancelUnicastTransmission,
+ AcknowledgeCancelUnicastTransmission,
+ PathTrace,
+ AlternateTimeOffsetIndicator,
+ Legacy(u16),
+ Experimental(u16),
+ OrganizationExtensionPropagate,
+ EnhancedAccuracyMetrics,
+ OrganizationExtensionDoNotPropagate,
+ L1Sync,
+ PortCommunicationAvailability,
+ ProtocolAddress,
+ SlaveRxSyncTimingData,
+ SlaveRxSyncComputedData,
+ SlaveTxEventTimestamps,
+ CumulativeRateRatio,
+ Pad,
+ Authentication,
+}
+
+impl TlvType {
+ pub fn to_primitive(self) -> u16 {
+ match self {
+ Self::Reserved(value) => value,
+ Self::Management => 0x0001,
+ Self::ManagementErrorStatus => 0x0002,
+ Self::OrganizationExtension => 0x0003,
+ Self::RequestUnicastTransmission => 0x0004,
+ Self::GrantUnicastTransmission => 0x0005,
+ Self::CancelUnicastTransmission => 0x0006,
+ Self::AcknowledgeCancelUnicastTransmission => 0x0007,
+ Self::PathTrace => 0x0008,
+ Self::AlternateTimeOffsetIndicator => 0x0009,
+ Self::Legacy(value) => value,
+ Self::Experimental(value) => value,
+ Self::OrganizationExtensionPropagate => 0x4000,
+ Self::EnhancedAccuracyMetrics => 0x4001,
+ Self::OrganizationExtensionDoNotPropagate => 0x8000,
+ Self::L1Sync => 0x8001,
+ Self::PortCommunicationAvailability => 0x8002,
+ Self::ProtocolAddress => 0x8003,
+ Self::SlaveRxSyncTimingData => 0x8004,
+ Self::SlaveRxSyncComputedData => 0x8005,
+ Self::SlaveTxEventTimestamps => 0x8006,
+ Self::CumulativeRateRatio => 0x8007,
+ Self::Pad => 0x8008,
+ Self::Authentication => 0x8009,
+ }
+ }
+
+ pub fn from_primitive(value: u16) -> Self {
+ match value {
+ 0x0000
+ | 0x000a..=0x1fff
+ | 0x2030..=0x3fff
+ | 0x4002..=0x7eff
+ | 0x800a..=0xffef
+ | 0xfff0..=0xffff => Self::Reserved(value),
+ 0x2000..=0x2003 => Self::Legacy(value),
+ 0x2004..=0x202f | 0x7f00..=0x7fff => Self::Experimental(value),
+ 0x0001 => Self::Management,
+ 0x0002 => Self::ManagementErrorStatus,
+ 0x0003 => Self::OrganizationExtension,
+ 0x0004 => Self::RequestUnicastTransmission,
+ 0x0005 => Self::GrantUnicastTransmission,
+ 0x0006 => Self::CancelUnicastTransmission,
+ 0x0007 => Self::AcknowledgeCancelUnicastTransmission,
+ 0x0008 => Self::PathTrace,
+ 0x0009 => Self::AlternateTimeOffsetIndicator,
+ 0x4000 => Self::OrganizationExtensionPropagate,
+ 0x4001 => Self::EnhancedAccuracyMetrics,
+ 0x8000 => Self::OrganizationExtensionDoNotPropagate,
+ 0x8001 => Self::L1Sync,
+ 0x8002 => Self::PortCommunicationAvailability,
+ 0x8003 => Self::ProtocolAddress,
+ 0x8004 => Self::SlaveRxSyncTimingData,
+ 0x8005 => Self::SlaveRxSyncComputedData,
+ 0x8006 => Self::SlaveTxEventTimestamps,
+ 0x8007 => Self::CumulativeRateRatio,
+ 0x8008 => Self::Pad,
+ 0x8009 => Self::Authentication,
+ }
+ }
+
+ // True if this message should be propagated by a boundary clock if it is
+ // attached to an announce message
+ pub fn announce_propagate(self) -> bool {
+ matches!(self.to_primitive(), 0x0008 | 0x0009 | 0x4000..=0x7fff)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn serialize_management() {
+ let tlv = Tlv {
+ tlv_type: TlvType::Management,
+ value: &b"hello!"[..],
+ };
+
+ let mut buffer = [0; 256];
+ tlv.serialize(&mut buffer).unwrap();
+
+ let n = tlv.wire_size();
+ assert_eq!(n, 10);
+
+ let decoded = Tlv::deserialize(&buffer[..n]).unwrap();
+
+ assert_eq!(tlv, decoded);
+ }
+
+ #[test]
+ fn parse_announce_propagate_messages() {
+ let mut alloc = [0; 256];
+ let mut buffer = &mut alloc[..];
+
+ let tlv1 = Tlv {
+ tlv_type: TlvType::Management,
+ value: &b"hello!"[..],
+ };
+ tlv1.serialize(buffer).unwrap();
+ buffer = &mut buffer[tlv1.wire_size()..];
+ assert!(!tlv1.tlv_type.announce_propagate());
+
+ let tlv2 = Tlv {
+ tlv_type: TlvType::PathTrace,
+ value: &b"PathTrace!"[..],
+ };
+ tlv2.serialize(buffer).unwrap();
+ buffer = &mut buffer[tlv2.wire_size()..];
+ assert!(tlv2.tlv_type.announce_propagate());
+
+ let tlv3 = Tlv {
+ tlv_type: TlvType::OrganizationExtensionPropagate,
+ value: &b"OrganizationExtensionPropagate"[..],
+ };
+ tlv3.serialize(buffer).unwrap();
+ buffer = &mut buffer[tlv3.wire_size()..];
+ assert!(tlv3.tlv_type.announce_propagate());
+
+ let _ = buffer;
+
+ let buffer = &mut alloc[..tlv1.wire_size() + tlv2.wire_size() + tlv3.wire_size()];
+ let tlv_set = TlvSet::deserialize(buffer).unwrap();
+ let mut it = tlv_set.announce_propagate_tlv();
+
+ assert_eq!(it.next(), Some(tlv2));
+ assert_eq!(it.next(), Some(tlv3));
+ assert_eq!(it.next(), None);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +
use crate::{
+ config::InstanceConfig,
+ datastructures::{
+ common::{ClockIdentity, ClockQuality},
+ messages::SdoId,
+ },
+};
+
+/// A concrete implementation of the PTP Default dataset (IEEE1588-2019 section
+/// 8.2.1)
+///
+/// This dataset describes the properties of the PTP instance. Most
+/// instance-wide configuration options are found here, with the exception of
+/// those related to timebase, which is contained in the
+/// [TimePropertiesDS](crate::TimePropertiesDS) dataset.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) struct DefaultDS {
+ pub(crate) clock_identity: ClockIdentity,
+ pub(crate) number_ports: u16,
+ pub(crate) clock_quality: ClockQuality,
+ pub(crate) priority_1: u8,
+ pub(crate) priority_2: u8,
+ pub(crate) domain_number: u8,
+ pub(crate) slave_only: bool,
+ pub(crate) sdo_id: SdoId,
+}
+
+impl DefaultDS {
+ pub(crate) fn new(config: InstanceConfig) -> Self {
+ Self {
+ clock_identity: config.clock_identity,
+ number_ports: 0,
+ clock_quality: Default::default(),
+ priority_1: config.priority_1,
+ priority_2: config.priority_2,
+ domain_number: config.domain_number,
+ slave_only: config.slave_only,
+ sdo_id: config.sdo_id,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +
use super::DefaultDS;
+use crate::datastructures::common::{ClockIdentity, ClockQuality, PortIdentity};
+
+// TODO: Discuss moving this (and TimePropertiesDS, ...) to slave?
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) struct ParentDS {
+ pub(crate) parent_port_identity: PortIdentity,
+ pub(crate) parent_stats: bool,
+ pub(crate) observed_parent_offset_scaled_log_variance: u16,
+ pub(crate) observed_parent_clock_phase_change_rate: u32,
+ pub(crate) grandmaster_identity: ClockIdentity,
+ pub(crate) grandmaster_clock_quality: ClockQuality,
+ pub(crate) grandmaster_priority_1: u8,
+ pub(crate) grandmaster_priority_2: u8,
+}
+
+impl ParentDS {
+ pub(crate) fn new(default_ds: DefaultDS) -> Self {
+ ParentDS {
+ parent_port_identity: PortIdentity {
+ clock_identity: default_ds.clock_identity,
+ port_number: 0,
+ },
+ parent_stats: false,
+ observed_parent_offset_scaled_log_variance: 0xffff,
+ observed_parent_clock_phase_change_rate: 0x7fffffff,
+ grandmaster_identity: default_ds.clock_identity,
+ grandmaster_clock_quality: default_ds.clock_quality,
+ grandmaster_priority_1: default_ds.priority_1,
+ grandmaster_priority_2: default_ds.priority_2,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +
use crate::datastructures::common::{LeapIndicator, TimeSource};
+
+/// A concrete implementation of the PTP Time Properties dataset (IEEE1588-2019
+/// section 8.2.4
+///
+/// This dataset describes the timescale currently in use, as well as any
+/// upcoming leap seconds on that timescale.
+#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
+pub struct TimePropertiesDS {
+ pub(crate) current_utc_offset: Option<i16>,
+ pub(crate) leap_indicator: LeapIndicator,
+ pub(crate) time_traceable: bool,
+ pub(crate) frequency_traceable: bool,
+ pub(crate) ptp_timescale: bool,
+ pub(crate) time_source: TimeSource,
+}
+
+impl TimePropertiesDS {
+ /// Create a Time Properties data set for the PTP timescale.
+ ///
+ /// This creates a dataset for the default PTP timescale, which is UTC
+ /// seconds since the PTP epoch excluding leap seconds. The traceability
+ /// properties indicate whether the current clock time and frequency can be
+ /// traced back to an internationally recognized standard in the metrology
+ /// sense of the word. When in doubt, just set these to false.
+ pub fn new_ptp_time(
+ current_utc_offset: Option<i16>,
+ leap_indicator: LeapIndicator,
+ time_traceable: bool,
+ frequency_traceable: bool,
+ time_source: TimeSource,
+ ) -> Self {
+ TimePropertiesDS {
+ current_utc_offset,
+ leap_indicator,
+ time_traceable,
+ frequency_traceable,
+ ptp_timescale: true,
+ time_source,
+ }
+ }
+
+ /// Create a Time Properties data set for an Arbitrary timescale
+ ///
+ /// The arbitrary timescale can be used when wanting to synchronize multiple
+ /// computers using PTP to a timescale that is unrelated to UTC. The
+ /// traceability properties indicate whether the current clock time and
+ /// frequency can be traced back to an internationally recognized standard
+ /// in the metrology sense of the word. When in doubt, just set these to
+ /// false.
+ pub fn new_arbitrary_time(
+ time_traceable: bool,
+ frequency_traceable: bool,
+ time_source: TimeSource,
+ ) -> Self {
+ TimePropertiesDS {
+ current_utc_offset: None,
+ leap_indicator: LeapIndicator::NoLeap,
+ time_traceable,
+ frequency_traceable,
+ ptp_timescale: false,
+ time_source,
+ }
+ }
+
+ /// Is the current timescale the ptp (utc-derived) timescale?
+ pub fn is_ptp(&self) -> bool {
+ self.ptp_timescale
+ }
+
+ pub fn leap_indicator(&self) -> LeapIndicator {
+ self.leap_indicator
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +
use super::Header;
+use crate::datastructures::{
+ common::{ClockIdentity, ClockQuality, LeapIndicator, TimeSource, WireTimestamp},
+ datasets::TimePropertiesDS,
+ WireFormat, WireFormatError,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct AnnounceMessage {
+ pub(crate) header: Header,
+ pub(crate) origin_timestamp: WireTimestamp,
+ pub(crate) current_utc_offset: i16,
+ pub(crate) grandmaster_priority_1: u8,
+ pub(crate) grandmaster_clock_quality: ClockQuality,
+ pub(crate) grandmaster_priority_2: u8,
+ pub(crate) grandmaster_identity: ClockIdentity,
+ pub(crate) steps_removed: u16,
+ pub(crate) time_source: TimeSource,
+}
+
+impl AnnounceMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 30
+ }
+
+ pub(crate) fn serialize_content(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ if buffer.len() < 30 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ self.origin_timestamp.serialize(&mut buffer[0..10])?;
+ buffer[10..12].copy_from_slice(&self.current_utc_offset.to_be_bytes());
+ buffer[13] = self.grandmaster_priority_1;
+ self.grandmaster_clock_quality
+ .serialize(&mut buffer[14..18])?;
+ buffer[18] = self.grandmaster_priority_2;
+ self.grandmaster_identity.serialize(&mut buffer[19..27])?;
+ buffer[27..29].copy_from_slice(&self.steps_removed.to_be_bytes());
+ buffer[29] = self.time_source.to_primitive();
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(
+ header: Header,
+ buffer: &[u8],
+ ) -> Result<Self, WireFormatError> {
+ if buffer.len() < 30 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ Ok(Self {
+ header,
+ origin_timestamp: WireTimestamp::deserialize(&buffer[0..10])?,
+ current_utc_offset: i16::from_be_bytes(buffer[10..12].try_into().unwrap()),
+ grandmaster_priority_1: buffer[13],
+ grandmaster_clock_quality: ClockQuality::deserialize(&buffer[14..18])?,
+ grandmaster_priority_2: buffer[18],
+ grandmaster_identity: ClockIdentity::deserialize(&buffer[19..27])?,
+ steps_removed: u16::from_be_bytes(buffer[27..29].try_into().unwrap()),
+ time_source: TimeSource::from_primitive(buffer[29]),
+ })
+ }
+
+ pub(crate) fn time_properties(&self) -> TimePropertiesDS {
+ let leap_indicator = if self.header.leap59 {
+ LeapIndicator::Leap59
+ } else if self.header.leap61 {
+ LeapIndicator::Leap61
+ } else {
+ LeapIndicator::NoLeap
+ };
+
+ let current_utc_offset = self
+ .header
+ .current_utc_offset_valid
+ .then_some(self.current_utc_offset);
+
+ TimePropertiesDS {
+ current_utc_offset,
+ leap_indicator,
+ time_traceable: self.header.time_tracable,
+ frequency_traceable: self.header.frequency_tracable,
+ ptp_timescale: self.header.ptp_timescale,
+ time_source: self.time_source,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::datastructures::common::ClockAccuracy;
+
+ #[test]
+ fn announce_wireformat() {
+ let representations = [(
+ [
+ 0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x73, 0x46, 0x60, 0x00, 0x00, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x80, 0x63, 0xff, 0xff, 0x00, 0x09, 0xba, 0xf8, 0x21, 0x00, 0x00,
+ 0x80, 0x80,
+ ],
+ AnnounceMessage {
+ header: Header::default(),
+ origin_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 175326816,
+ },
+ current_utc_offset: 0,
+ grandmaster_priority_1: 96,
+ grandmaster_clock_quality: ClockQuality {
+ clock_class: 0,
+ clock_accuracy: ClockAccuracy::Reserved,
+ offset_scaled_log_variance: 128,
+ },
+ grandmaster_priority_2: 99,
+ grandmaster_identity: ClockIdentity([
+ 0xff, 0xff, 0x00, 0x09, 0xba, 0xf8, 0x21, 0x00,
+ ]),
+ steps_removed: 128,
+ time_source: TimeSource::Unknown(0x80),
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 30];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ AnnounceMessage::deserialize_content(Header::default(), &byte_representation)
+ .unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +
use super::MessageType;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) enum ControlField {
+ Sync,
+ DelayReq,
+ FollowUp,
+ DelayResp,
+ Management,
+ AllOthers,
+}
+
+impl ControlField {
+ pub fn to_primitive(self) -> u8 {
+ match self {
+ ControlField::Sync => 0x00,
+ ControlField::DelayReq => 0x01,
+ ControlField::FollowUp => 0x02,
+ ControlField::DelayResp => 0x03,
+ ControlField::Management => 0x04,
+ ControlField::AllOthers => 0x05,
+ }
+ }
+}
+
+impl From<MessageType> for ControlField {
+ fn from(message_type: MessageType) -> Self {
+ match message_type {
+ MessageType::Sync => ControlField::Sync,
+ MessageType::DelayReq => ControlField::DelayReq,
+ MessageType::FollowUp => ControlField::FollowUp,
+ MessageType::DelayResp => ControlField::DelayResp,
+ MessageType::Management => ControlField::Management,
+ _ => ControlField::AllOthers,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +
use crate::datastructures::{common::WireTimestamp, WireFormat, WireFormatError};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct DelayReqMessage {
+ pub(crate) origin_timestamp: WireTimestamp,
+}
+
+impl DelayReqMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 10
+ }
+
+ pub(crate) fn serialize_content(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ self.origin_timestamp.serialize(&mut buffer[0..10])?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ let slice = buffer.get(0..10).ok_or(WireFormatError::BufferTooShort)?;
+ let origin_timestamp = WireTimestamp::deserialize(slice)?;
+
+ Ok(Self { origin_timestamp })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0],
+ DelayReqMessage {
+ origin_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 10];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ DelayReqMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +
use crate::datastructures::{
+ common::{PortIdentity, WireTimestamp},
+ WireFormat, WireFormatError,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct DelayRespMessage {
+ pub(crate) receive_timestamp: WireTimestamp,
+ pub(crate) requesting_port_identity: PortIdentity,
+}
+
+impl DelayRespMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 20
+ }
+
+ pub(crate) fn serialize_content(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ self.receive_timestamp.serialize(&mut buffer[0..10])?;
+ self.requesting_port_identity
+ .serialize(&mut buffer[10..20])?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ let slice = buffer.get(0..20).ok_or(WireFormatError::BufferTooShort)?;
+ let receive_timestamp = WireTimestamp::deserialize(&slice[0..10])?;
+ let requesting_port_identity = PortIdentity::deserialize(&slice[10..20])?;
+
+ Ok(Self {
+ receive_timestamp,
+ requesting_port_identity,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::datastructures::common::{ClockIdentity, PortIdentity};
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [
+ 0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ ],
+ DelayRespMessage {
+ receive_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ requesting_port_identity: PortIdentity {
+ clock_identity: ClockIdentity([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
+ port_number: 0x090a,
+ },
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 20];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ DelayRespMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +
use crate::datastructures::{common::WireTimestamp, WireFormat, WireFormatError};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct FollowUpMessage {
+ pub(crate) precise_origin_timestamp: WireTimestamp,
+}
+
+impl FollowUpMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 10
+ }
+
+ pub(crate) fn serialize_content(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ self.precise_origin_timestamp
+ .serialize(&mut buffer[0..10])?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ let slice = buffer.get(0..10).ok_or(WireFormatError::BufferTooShort)?;
+ let precise_origin_timestamp = WireTimestamp::deserialize(slice)?;
+
+ Ok(Self {
+ precise_origin_timestamp,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [
+ (
+ [0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0],
+ FollowUpMessage {
+ precise_origin_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ },
+ ),
+ (
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01u8],
+ FollowUpMessage {
+ precise_origin_timestamp: WireTimestamp {
+ seconds: 0x0000_0000_0002,
+ nanos: 0x0000_0001,
+ },
+ },
+ ),
+ ];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 10];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ FollowUpMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +
use super::{control_field::ControlField, MessageType};
+use crate::datastructures::{
+ common::{PortIdentity, TimeInterval},
+ WireFormat, WireFormatError,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Header {
+ pub(crate) sdo_id: SdoId,
+ pub(crate) version: PtpVersion,
+ pub(crate) domain_number: u8,
+ pub(crate) alternate_master_flag: bool,
+ pub(crate) two_step_flag: bool,
+ pub(crate) unicast_flag: bool,
+ pub(crate) ptp_profile_specific_1: bool,
+ pub(crate) ptp_profile_specific_2: bool,
+ pub(crate) leap61: bool,
+ pub(crate) leap59: bool,
+ pub(crate) current_utc_offset_valid: bool,
+ pub(crate) ptp_timescale: bool,
+ pub(crate) time_tracable: bool,
+ pub(crate) frequency_tracable: bool,
+ pub(crate) synchronization_uncertain: bool,
+ pub(crate) correction_field: TimeInterval,
+ pub(crate) source_port_identity: PortIdentity,
+ pub(crate) sequence_id: u16,
+ pub(crate) log_message_interval: i8,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct DeserializedHeader {
+ pub(crate) header: Header,
+ pub(crate) message_type: MessageType,
+ pub(crate) message_length: u16,
+}
+
+impl Header {
+ pub(super) fn new() -> Self {
+ Self {
+ sdo_id: SdoId(0),
+ version: PtpVersion { major: 2, minor: 1 },
+ domain_number: 0,
+ alternate_master_flag: false,
+ two_step_flag: false,
+ unicast_flag: false,
+ ptp_profile_specific_1: false,
+ ptp_profile_specific_2: false,
+ leap59: false,
+ leap61: false,
+ current_utc_offset_valid: false,
+ ptp_timescale: false,
+ time_tracable: false,
+ frequency_tracable: false,
+ synchronization_uncertain: false,
+ correction_field: TimeInterval::default(),
+ source_port_identity: PortIdentity::default(),
+ sequence_id: 0,
+ log_message_interval: 0,
+ }
+ }
+
+ pub(crate) fn wire_size(&self) -> usize {
+ 34
+ }
+
+ pub(crate) fn serialize_header(
+ &self,
+ content_type: MessageType,
+ content_length: usize,
+ buffer: &mut [u8],
+ ) -> Result<(), WireFormatError> {
+ buffer[0] = ((self.sdo_id.high_byte()) << 4) | ((content_type as u8) & 0x0f);
+ buffer[1] = self.version.as_byte();
+ buffer[2..4].copy_from_slice(&((content_length + self.wire_size()) as u16).to_be_bytes());
+ buffer[4] = self.domain_number;
+ buffer[5] = self.sdo_id.low_byte();
+ buffer[6] = 0;
+ buffer[7] = 0;
+ buffer[6] |= self.alternate_master_flag as u8;
+ buffer[6] |= (self.two_step_flag as u8) << 1;
+ buffer[6] |= (self.unicast_flag as u8) << 2;
+ buffer[6] |= (self.ptp_profile_specific_1 as u8) << 5;
+ buffer[6] |= (self.ptp_profile_specific_2 as u8) << 6;
+ buffer[7] |= self.leap61 as u8;
+ buffer[7] |= (self.leap59 as u8) << 1;
+ buffer[7] |= (self.current_utc_offset_valid as u8) << 2;
+ buffer[7] |= (self.ptp_timescale as u8) << 3;
+ buffer[7] |= (self.time_tracable as u8) << 4;
+ buffer[7] |= (self.frequency_tracable as u8) << 5;
+ buffer[7] |= (self.synchronization_uncertain as u8) << 6;
+ self.correction_field.serialize(&mut buffer[8..16])?;
+ buffer[16..20].copy_from_slice(&[0, 0, 0, 0]);
+ self.source_port_identity.serialize(&mut buffer[20..30])?;
+ buffer[30..32].copy_from_slice(&self.sequence_id.to_be_bytes());
+ buffer[32] = ControlField::from(content_type).to_primitive();
+ buffer[33] = self.log_message_interval as u8;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_header(buffer: &[u8]) -> Result<DeserializedHeader, WireFormatError> {
+ if buffer.len() < 34 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ let version = PtpVersion::from_byte(buffer[1]);
+ let sdo_id = SdoId((((buffer[0] & 0xf0) as u16) << 4) | (buffer[5] as u16));
+
+ Ok(DeserializedHeader {
+ header: Self {
+ sdo_id,
+ version,
+ domain_number: buffer[4],
+ alternate_master_flag: (buffer[6] & (1 << 0)) > 0,
+ two_step_flag: (buffer[6] & (1 << 1)) > 0,
+ unicast_flag: (buffer[6] & (1 << 2)) > 0,
+ ptp_profile_specific_1: (buffer[6] & (1 << 5)) > 0,
+ ptp_profile_specific_2: (buffer[6] & (1 << 6)) > 0,
+ leap61: (buffer[7] & (1 << 0)) > 0,
+ leap59: (buffer[7] & (1 << 1)) > 0,
+ current_utc_offset_valid: (buffer[7] & (1 << 2)) > 0,
+ ptp_timescale: (buffer[7] & (1 << 3)) > 0,
+ time_tracable: (buffer[7] & (1 << 4)) > 0,
+ frequency_tracable: (buffer[7] & (1 << 5)) > 0,
+ synchronization_uncertain: (buffer[7] & (1 << 6)) > 0,
+ correction_field: TimeInterval::deserialize(&buffer[8..16])?,
+ source_port_identity: PortIdentity::deserialize(&buffer[20..30])?,
+ sequence_id: u16::from_be_bytes(buffer[30..32].try_into().unwrap()),
+ log_message_interval: buffer[33] as i8,
+ },
+ message_type: (buffer[0] & 0x0f).try_into()?,
+ message_length: u16::from_be_bytes(buffer[2..4].try_into().unwrap()),
+ })
+ }
+}
+
+impl Default for Header {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// A wrapper type for PTP Sdo Identifiers.
+///
+/// This is a separate type as sdo identifiers should be in the range 0-4095
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
+pub struct SdoId(u16);
+
+impl core::fmt::Display for SdoId {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl SdoId {
+ /// Create a new sdo id
+ ///
+ /// This function only returns an `SdoId` instance if the given identifier
+ /// is actually between 0 and 4095. Otherwise, `None` is returned.
+ pub fn new(sdo_id: u16) -> Option<Self> {
+ (0..=0x1000).contains(&sdo_id).then_some(Self(sdo_id))
+ }
+
+ const fn high_byte(self) -> u8 {
+ (self.0 >> 8) as u8
+ }
+
+ const fn low_byte(self) -> u8 {
+ self.0 as u8
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct PtpVersion {
+ major: u8,
+ minor: u8,
+}
+
+impl PtpVersion {
+ #[allow(unused)]
+ pub fn new(major: u8, minor: u8) -> Option<Self> {
+ if major >= 0x10 || minor >= 0x10 {
+ None
+ } else {
+ Some(Self { major, minor })
+ }
+ }
+
+ fn as_byte(&self) -> u8 {
+ self.minor << 4 | self.major
+ }
+
+ fn from_byte(byte: u8) -> Self {
+ Self {
+ major: byte & 0x0f,
+ minor: byte >> 4,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use fixed::types::I48F16;
+
+ use super::*;
+ use crate::datastructures::common::ClockIdentity;
+
+ #[test]
+ fn flagfield_wireformat() {
+ #[rustfmt::skip]
+ let representations = [
+ ([0x00, 0x00u8], Header::default()),
+ ([0x01, 0x00u8], Header { alternate_master_flag: true, ..Default::default() }),
+ ([0x02, 0x00u8], Header { two_step_flag: true, ..Default::default() }),
+ ([0x04, 0x00u8], Header { unicast_flag: true, ..Default::default() }),
+ ([0x20, 0x00u8], Header { ptp_profile_specific_1: true, ..Default::default() }),
+ ([0x40, 0x00u8], Header { ptp_profile_specific_2: true, ..Default::default() }),
+ ([0x00, 0x01u8], Header { leap61: true, ..Default::default() }),
+ ([0x00, 0x02u8], Header { leap59: true, ..Default::default() }),
+ ([0x00, 0x04u8], Header { current_utc_offset_valid: true, ..Default::default() }),
+ ([0x00, 0x08u8], Header { ptp_timescale: true, ..Default::default() }),
+ ([0x00, 0x10u8], Header { time_tracable: true, ..Default::default() }),
+ ([0x00, 0x20u8], Header { frequency_tracable: true, ..Default::default() }),
+ ([0x00, 0x40u8], Header { synchronization_uncertain: true, ..Default::default() }),
+ ];
+
+ for (i, (byte_representation, flag_representation)) in
+ representations.into_iter().enumerate()
+ {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 34];
+ flag_representation
+ .serialize_header(MessageType::Sync, 0, &mut serialization_buffer)
+ .unwrap();
+ assert_eq!(
+ serialization_buffer[6..8],
+ byte_representation,
+ "The serialized flag field is not what it's supposed to for variant {}",
+ i
+ );
+
+ // Test the deserialization output
+ serialization_buffer[6] = byte_representation[0];
+ serialization_buffer[7] = byte_representation[1];
+ let deserialized_flag_field =
+ Header::deserialize_header(&serialization_buffer).unwrap();
+ assert_eq!(
+ deserialized_flag_field.header, flag_representation,
+ "The deserialized flag field is not what it's supposed to for variant {}",
+ i
+ );
+ }
+ }
+
+ #[test]
+ fn header_wireformat() {
+ let representations = [(
+ [
+ 0x59,
+ 0xa1,
+ 0x12,
+ 0x34,
+ 0xaa,
+ 0xbb,
+ 0b0100_0101,
+ 0b0010_1010,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x01,
+ 0x80,
+ 0x00,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 0x55,
+ 0x55,
+ 0xde,
+ 0xad,
+ 0x03,
+ 0x16,
+ ],
+ DeserializedHeader {
+ header: Header {
+ sdo_id: SdoId(0x5bb),
+ version: PtpVersion {
+ major: 0x1,
+ minor: 0xa,
+ },
+ domain_number: 0xaa,
+ alternate_master_flag: true,
+ two_step_flag: false,
+ unicast_flag: true,
+ ptp_profile_specific_1: false,
+ ptp_profile_specific_2: true,
+ leap61: false,
+ leap59: true,
+ current_utc_offset_valid: false,
+ ptp_timescale: true,
+ time_tracable: false,
+ frequency_tracable: true,
+ synchronization_uncertain: false,
+ correction_field: TimeInterval(I48F16::from_num(1.5f64)),
+ source_port_identity: PortIdentity {
+ clock_identity: ClockIdentity([0, 1, 2, 3, 4, 5, 6, 7]),
+ port_number: 0x5555,
+ },
+ sequence_id: 0xdead,
+ log_message_interval: 0x16,
+ },
+ message_type: MessageType::DelayResp,
+ message_length: 0x1234,
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 34];
+ object_representation
+ .header
+ .serialize_header(
+ object_representation.message_type,
+ object_representation.message_length as usize
+ - object_representation.header.wire_size(),
+ &mut serialization_buffer,
+ )
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = Header::deserialize_header(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +
use crate::datastructures::{common::PortIdentity, WireFormat, WireFormatError};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct ManagementMessage {
+ pub(super) target_port_identity: PortIdentity,
+ pub(super) starting_boundary_hops: u8,
+ pub(super) boundary_hops: u8,
+ pub(super) action: ManagementAction,
+}
+
+impl ManagementMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 14
+ }
+
+ pub(crate) fn serialize_content(
+ &self,
+ buffer: &mut [u8],
+ ) -> Result<(), crate::datastructures::WireFormatError> {
+ self.target_port_identity.serialize(&mut buffer[0..10])?;
+ buffer[11] = self.starting_boundary_hops;
+ buffer[12] = self.boundary_hops;
+ buffer[13] = self.action.to_primitive();
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(
+ buffer: &[u8],
+ ) -> Result<Self, crate::datastructures::WireFormatError> {
+ if buffer.len() < 14 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+ Ok(Self {
+ target_port_identity: PortIdentity::deserialize(&buffer[0..10])?,
+ starting_boundary_hops: buffer[11],
+ boundary_hops: buffer[12],
+ action: ManagementAction::from_primitive(buffer[13]),
+ })
+ }
+}
+
+/// See: 15.4.1.6
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[allow(clippy::upper_case_acronyms)]
+pub(crate) enum ManagementAction {
+ Reserved,
+ GET,
+ SET,
+ RESPONSE,
+ COMMAND,
+ ACKNOWLEDGE,
+}
+
+impl ManagementAction {
+ pub fn to_primitive(self) -> u8 {
+ match self {
+ Self::GET => 0x0,
+ Self::SET => 0x1,
+ Self::RESPONSE => 0x2,
+ Self::COMMAND => 0x3,
+ Self::ACKNOWLEDGE => 0x4,
+ Self::Reserved => 0x5,
+ }
+ }
+
+ pub fn from_primitive(value: u8) -> Self {
+ match value {
+ 0x0 => Self::GET,
+ 0x1 => Self::SET,
+ 0x2 => Self::RESPONSE,
+ 0x3 => Self::COMMAND,
+ 0x4 => Self::ACKNOWLEDGE,
+ 0x5..=u8::MAX => Self::Reserved,
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +
//! Ptp network messages
+
+pub(crate) use announce::*;
+pub(crate) use delay_req::*;
+pub(crate) use delay_resp::*;
+pub(crate) use follow_up::*;
+pub use header::*;
+pub(crate) use sync::*;
+
+use self::{
+ management::ManagementMessage, p_delay_req::PDelayReqMessage, p_delay_resp::PDelayRespMessage,
+ p_delay_resp_follow_up::PDelayRespFollowUpMessage, signalling::SignalingMessage,
+};
+use super::{
+ common::{PortIdentity, TimeInterval, TlvSet, WireTimestamp},
+ datasets::DefaultDS,
+};
+use crate::{ptp_instance::PtpInstanceState, Interval, LeapIndicator, Time};
+
+mod announce;
+mod control_field;
+mod delay_req;
+mod delay_resp;
+mod follow_up;
+mod header;
+mod management;
+mod p_delay_req;
+mod p_delay_resp;
+mod p_delay_resp_follow_up;
+mod signalling;
+mod sync;
+
+pub const MAX_DATA_LEN: usize = 255;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum MessageType {
+ Sync = 0x0,
+ DelayReq = 0x1,
+ PDelayReq = 0x2,
+ PDelayResp = 0x3,
+ FollowUp = 0x8,
+ DelayResp = 0x9,
+ PDelayRespFollowUp = 0xa,
+ Announce = 0xb,
+ Signaling = 0xc,
+ Management = 0xd,
+}
+
+pub struct EnumConversionError(u8);
+
+impl TryFrom<u8> for MessageType {
+ type Error = EnumConversionError;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ use MessageType::*;
+
+ match value {
+ 0x0 => Ok(Sync),
+ 0x1 => Ok(DelayReq),
+ 0x2 => Ok(PDelayReq),
+ 0x3 => Ok(PDelayResp),
+ 0x8 => Ok(FollowUp),
+ 0x9 => Ok(DelayResp),
+ 0xa => Ok(PDelayRespFollowUp),
+ 0xb => Ok(Announce),
+ 0xc => Ok(Signaling),
+ 0xd => Ok(Management),
+ _ => Err(EnumConversionError(value)),
+ }
+ }
+}
+
+#[cfg(feature = "fuzz")]
+pub use fuzz::FuzzMessage;
+
+#[cfg(feature = "fuzz")]
+mod fuzz {
+ use super::*;
+ use crate::datastructures::{common::Tlv, WireFormatError};
+
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct FuzzMessage<'a> {
+ inner: Message<'a>,
+ }
+
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct FuzzTlv<'a>(Tlv<'a>);
+
+ impl<'a> FuzzMessage<'a> {
+ pub fn deserialize(buffer: &'a [u8]) -> Result<Self, impl std::error::Error> {
+ Ok::<FuzzMessage, WireFormatError>(FuzzMessage {
+ inner: Message::deserialize(buffer)?,
+ })
+ }
+
+ pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize, impl std::error::Error> {
+ self.inner.serialize(buffer)
+ }
+
+ pub fn tlv(&self) -> impl Iterator<Item = FuzzTlv<'_>> + '_ {
+ self.inner.suffix.tlv().map(FuzzTlv)
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Message<'a> {
+ pub(crate) header: Header,
+ pub(crate) body: MessageBody,
+ pub(crate) suffix: TlvSet<'a>,
+}
+
+impl<'a> Message<'a> {
+ pub(crate) fn is_event(&self) -> bool {
+ use MessageBody::*;
+ match self.body {
+ Sync(_) | DelayReq(_) | PDelayReq(_) | PDelayResp(_) => true,
+ FollowUp(_)
+ | DelayResp(_)
+ | PDelayRespFollowUp(_)
+ | Announce(_)
+ | Signaling(_)
+ | Management(_) => false,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) enum MessageBody {
+ Sync(SyncMessage),
+ DelayReq(DelayReqMessage),
+ PDelayReq(PDelayReqMessage),
+ PDelayResp(PDelayRespMessage),
+ FollowUp(FollowUpMessage),
+ DelayResp(DelayRespMessage),
+ PDelayRespFollowUp(PDelayRespFollowUpMessage),
+ Announce(AnnounceMessage),
+ Signaling(SignalingMessage),
+ Management(ManagementMessage),
+}
+
+impl MessageBody {
+ fn wire_size(&self) -> usize {
+ match &self {
+ MessageBody::Sync(m) => m.content_size(),
+ MessageBody::DelayReq(m) => m.content_size(),
+ MessageBody::PDelayReq(m) => m.content_size(),
+ MessageBody::PDelayResp(m) => m.content_size(),
+ MessageBody::FollowUp(m) => m.content_size(),
+ MessageBody::DelayResp(m) => m.content_size(),
+ MessageBody::PDelayRespFollowUp(m) => m.content_size(),
+ MessageBody::Announce(m) => m.content_size(),
+ MessageBody::Signaling(m) => m.content_size(),
+ MessageBody::Management(m) => m.content_size(),
+ }
+ }
+
+ fn content_type(&self) -> MessageType {
+ match self {
+ MessageBody::Sync(_) => MessageType::Sync,
+ MessageBody::DelayReq(_) => MessageType::DelayReq,
+ MessageBody::PDelayReq(_) => MessageType::PDelayReq,
+ MessageBody::PDelayResp(_) => MessageType::PDelayResp,
+ MessageBody::FollowUp(_) => MessageType::FollowUp,
+ MessageBody::DelayResp(_) => MessageType::DelayResp,
+ MessageBody::PDelayRespFollowUp(_) => MessageType::PDelayRespFollowUp,
+ MessageBody::Announce(_) => MessageType::Announce,
+ MessageBody::Signaling(_) => MessageType::Signaling,
+ MessageBody::Management(_) => MessageType::Management,
+ }
+ }
+
+ pub(crate) fn serialize(&self, buffer: &mut [u8]) -> Result<usize, super::WireFormatError> {
+ match &self {
+ MessageBody::Sync(m) => m.serialize_content(buffer)?,
+ MessageBody::DelayReq(m) => m.serialize_content(buffer)?,
+ MessageBody::PDelayReq(m) => m.serialize_content(buffer)?,
+ MessageBody::PDelayResp(m) => m.serialize_content(buffer)?,
+ MessageBody::FollowUp(m) => m.serialize_content(buffer)?,
+ MessageBody::DelayResp(m) => m.serialize_content(buffer)?,
+ MessageBody::PDelayRespFollowUp(m) => m.serialize_content(buffer)?,
+ MessageBody::Announce(m) => m.serialize_content(buffer)?,
+ MessageBody::Signaling(m) => m.serialize_content(buffer)?,
+ MessageBody::Management(m) => m.serialize_content(buffer)?,
+ }
+
+ Ok(self.wire_size())
+ }
+
+ pub(crate) fn deserialize(
+ message_type: MessageType,
+ header: &Header,
+ buffer: &[u8],
+ ) -> Result<Self, super::WireFormatError> {
+ let body = match message_type {
+ MessageType::Sync => MessageBody::Sync(SyncMessage::deserialize_content(buffer)?),
+ MessageType::DelayReq => {
+ MessageBody::DelayReq(DelayReqMessage::deserialize_content(buffer)?)
+ }
+ MessageType::PDelayReq => {
+ MessageBody::PDelayReq(PDelayReqMessage::deserialize_content(buffer)?)
+ }
+ MessageType::PDelayResp => {
+ MessageBody::PDelayResp(PDelayRespMessage::deserialize_content(buffer)?)
+ }
+ MessageType::FollowUp => {
+ MessageBody::FollowUp(FollowUpMessage::deserialize_content(buffer)?)
+ }
+ MessageType::DelayResp => {
+ MessageBody::DelayResp(DelayRespMessage::deserialize_content(buffer)?)
+ }
+ MessageType::PDelayRespFollowUp => MessageBody::PDelayRespFollowUp(
+ PDelayRespFollowUpMessage::deserialize_content(buffer)?,
+ ),
+ MessageType::Announce => {
+ MessageBody::Announce(AnnounceMessage::deserialize_content(*header, buffer)?)
+ }
+ MessageType::Signaling => {
+ MessageBody::Signaling(SignalingMessage::deserialize_content(buffer)?)
+ }
+ MessageType::Management => {
+ MessageBody::Management(ManagementMessage::deserialize_content(buffer)?)
+ }
+ };
+
+ Ok(body)
+ }
+}
+
+fn base_header(default_ds: &DefaultDS, port_identity: PortIdentity, sequence_id: u16) -> Header {
+ Header {
+ sdo_id: default_ds.sdo_id,
+ domain_number: default_ds.domain_number,
+ source_port_identity: port_identity,
+ sequence_id,
+ ..Default::default()
+ }
+}
+
+impl Message<'_> {
+ pub(crate) fn sync(
+ default_ds: &DefaultDS,
+ port_identity: PortIdentity,
+ sequence_id: u16,
+ ) -> Self {
+ let header = Header {
+ two_step_flag: true,
+ ..base_header(default_ds, port_identity, sequence_id)
+ };
+
+ Message {
+ header,
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Default::default(),
+ }),
+ suffix: TlvSet::default(),
+ }
+ }
+
+ pub(crate) fn follow_up(
+ default_ds: &DefaultDS,
+ port_identity: PortIdentity,
+ sequence_id: u16,
+ timestamp: Time,
+ ) -> Self {
+ let header = Header {
+ correction_field: timestamp.subnano(),
+ ..base_header(default_ds, port_identity, sequence_id)
+ };
+
+ Message {
+ header,
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: timestamp.into(),
+ }),
+ suffix: TlvSet::default(),
+ }
+ }
+
+ pub(crate) fn announce(
+ global: &PtpInstanceState,
+ port_identity: PortIdentity,
+ sequence_id: u16,
+ ) -> Self {
+ let time_properties_ds = &global.time_properties_ds;
+
+ let header = Header {
+ leap59: time_properties_ds.leap_indicator == LeapIndicator::Leap59,
+ leap61: time_properties_ds.leap_indicator == LeapIndicator::Leap61,
+ current_utc_offset_valid: time_properties_ds.current_utc_offset.is_some(),
+ ptp_timescale: time_properties_ds.ptp_timescale,
+ time_tracable: time_properties_ds.time_traceable,
+ frequency_tracable: time_properties_ds.frequency_traceable,
+ ..base_header(&global.default_ds, port_identity, sequence_id)
+ };
+
+ let body = MessageBody::Announce(AnnounceMessage {
+ header,
+ origin_timestamp: Default::default(),
+ current_utc_offset: time_properties_ds.current_utc_offset.unwrap_or_default(),
+ grandmaster_priority_1: global.parent_ds.grandmaster_priority_1,
+ grandmaster_clock_quality: global.parent_ds.grandmaster_clock_quality,
+ grandmaster_priority_2: global.parent_ds.grandmaster_priority_2,
+ grandmaster_identity: global.parent_ds.grandmaster_identity,
+ steps_removed: global.current_ds.steps_removed,
+ time_source: time_properties_ds.time_source,
+ });
+
+ Message {
+ header,
+ body,
+ suffix: TlvSet::default(),
+ }
+ }
+
+ pub(crate) fn delay_req(
+ default_ds: &DefaultDS,
+ port_identity: PortIdentity,
+ sequence_id: u16,
+ ) -> Self {
+ let header = Header {
+ log_message_interval: 0x7f,
+ ..base_header(default_ds, port_identity, sequence_id)
+ };
+
+ Message {
+ header,
+ body: MessageBody::DelayReq(DelayReqMessage {
+ origin_timestamp: WireTimestamp::default(),
+ }),
+ suffix: TlvSet::default(),
+ }
+ }
+
+ pub(crate) fn delay_resp(
+ request_header: Header,
+ request: DelayReqMessage,
+ port_identity: PortIdentity,
+ min_delay_req_interval: Interval,
+ timestamp: Time,
+ ) -> Self {
+ // TODO is it really correct that we don't use any of the data?
+ let _ = request;
+
+ let header = Header {
+ two_step_flag: false,
+ source_port_identity: port_identity,
+ correction_field: TimeInterval(
+ request_header.correction_field.0 + timestamp.subnano().0,
+ ),
+ log_message_interval: min_delay_req_interval.as_log_2(),
+ ..request_header
+ };
+
+ let body = MessageBody::DelayResp(DelayRespMessage {
+ receive_timestamp: timestamp.into(),
+ requesting_port_identity: request_header.source_port_identity,
+ });
+
+ Message {
+ header,
+ body,
+ suffix: TlvSet::default(),
+ }
+ }
+}
+
+impl<'a> Message<'a> {
+ pub(crate) fn header(&self) -> &Header {
+ &self.header
+ }
+
+ /// The byte size on the wire of this message
+ pub(crate) fn wire_size(&self) -> usize {
+ self.header.wire_size() + self.body.wire_size() + self.suffix.wire_size()
+ }
+
+ /// Serializes the object into the PTP wire format.
+ ///
+ /// Returns the used buffer size that contains the message or an error.
+ pub(crate) fn serialize(&self, buffer: &mut [u8]) -> Result<usize, super::WireFormatError> {
+ let (header, rest) = buffer.split_at_mut(34);
+ let (body, tlv) = rest.split_at_mut(self.body.wire_size());
+
+ self.header
+ .serialize_header(
+ self.body.content_type(),
+ self.body.wire_size() + self.suffix.wire_size(),
+ header,
+ )
+ .unwrap();
+
+ self.body.serialize(body).unwrap();
+
+ self.suffix.serialize(tlv).unwrap();
+
+ Ok(self.wire_size())
+ }
+
+ /// Deserializes a message from the PTP wire format.
+ ///
+ /// Returns the message or an error.
+ pub(crate) fn deserialize(buffer: &'a [u8]) -> Result<Self, super::WireFormatError> {
+ let header_data = Header::deserialize_header(buffer)?;
+
+ // Skip the header bytes and only keep the content
+ let content_buffer = &buffer[34..];
+
+ let body = MessageBody::deserialize(
+ header_data.message_type,
+ &header_data.header,
+ content_buffer,
+ )?;
+
+ let tlv_buffer = &content_buffer
+ .get(body.wire_size()..)
+ .ok_or(super::WireFormatError::BufferTooShort)?;
+ let suffix = TlvSet::deserialize(tlv_buffer)?;
+
+ Ok(Message {
+ header: header_data.header,
+ body,
+ suffix,
+ })
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +
use crate::datastructures::{common::WireTimestamp, WireFormat, WireFormatError};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct PDelayReqMessage {
+ pub(super) origin_timestamp: WireTimestamp,
+}
+
+impl PDelayReqMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 10
+ }
+
+ pub(crate) fn serialize_content(
+ &self,
+ buffer: &mut [u8],
+ ) -> Result<(), crate::datastructures::WireFormatError> {
+ if buffer.len() < 10 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ self.origin_timestamp.serialize(&mut buffer[0..10])?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(
+ buffer: &[u8],
+ ) -> Result<Self, crate::datastructures::WireFormatError> {
+ let slice = buffer.get(0..10).ok_or(WireFormatError::BufferTooShort)?;
+
+ Ok(Self {
+ origin_timestamp: WireTimestamp::deserialize(slice)?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0],
+ PDelayReqMessage {
+ origin_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 10];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ PDelayReqMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +
use crate::datastructures::{
+ common::{PortIdentity, WireTimestamp},
+ WireFormat, WireFormatError,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct PDelayRespMessage {
+ pub(super) request_receive_timestamp: WireTimestamp,
+ pub(super) requesting_port_identity: PortIdentity,
+}
+
+impl PDelayRespMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 20
+ }
+
+ pub(crate) fn serialize_content(
+ &self,
+ buffer: &mut [u8],
+ ) -> Result<(), crate::datastructures::WireFormatError> {
+ if buffer.len() < 20 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+ self.request_receive_timestamp
+ .serialize(&mut buffer[0..10])?;
+ self.requesting_port_identity
+ .serialize(&mut buffer[10..20])?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(
+ buffer: &[u8],
+ ) -> Result<Self, crate::datastructures::WireFormatError> {
+ if buffer.len() < 20 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+ Ok(Self {
+ request_receive_timestamp: WireTimestamp::deserialize(&buffer[0..10])?,
+ requesting_port_identity: PortIdentity::deserialize(&buffer[10..20])?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::datastructures::common::{ClockIdentity, PortIdentity};
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [
+ 0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ ],
+ PDelayRespMessage {
+ request_receive_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ requesting_port_identity: PortIdentity {
+ clock_identity: ClockIdentity([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
+ port_number: 0x090a,
+ },
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 20];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ PDelayRespMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +
use crate::datastructures::{
+ common::{PortIdentity, WireTimestamp},
+ WireFormat, WireFormatError,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct PDelayRespFollowUpMessage {
+ pub(super) response_origin_timestamp: WireTimestamp,
+ pub(super) requesting_port_identity: PortIdentity,
+}
+
+impl PDelayRespFollowUpMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 20
+ }
+
+ pub(crate) fn serialize_content(
+ &self,
+ buffer: &mut [u8],
+ ) -> Result<(), crate::datastructures::WireFormatError> {
+ if buffer.len() < 20 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ self.response_origin_timestamp
+ .serialize(&mut buffer[0..10])?;
+ self.requesting_port_identity
+ .serialize(&mut buffer[10..20])?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(
+ buffer: &[u8],
+ ) -> Result<Self, crate::datastructures::WireFormatError> {
+ if buffer.len() < 20 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+ Ok(Self {
+ response_origin_timestamp: WireTimestamp::deserialize(&buffer[0..10])?,
+ requesting_port_identity: PortIdentity::deserialize(&buffer[10..20])?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::datastructures::common::{ClockIdentity, PortIdentity};
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [
+ 0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ ],
+ PDelayRespFollowUpMessage {
+ response_origin_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ requesting_port_identity: PortIdentity {
+ clock_identity: ClockIdentity([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
+ port_number: 0x090a,
+ },
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 20];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data =
+ PDelayRespFollowUpMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +
use crate::datastructures::{common::PortIdentity, WireFormat, WireFormatError};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct SignalingMessage {
+ pub(super) target_port_identity: PortIdentity,
+}
+
+impl SignalingMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 10
+ }
+
+ pub(crate) fn serialize_content(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ if buffer.len() < 10 {
+ return Err(WireFormatError::BufferTooShort);
+ }
+
+ let (left, _) = buffer.split_at_mut(10);
+
+ self.target_port_identity.serialize(left)?;
+
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ let identity_bytes = buffer.get(0..10).ok_or(WireFormatError::BufferTooShort)?;
+ let target_port_identity = PortIdentity::deserialize(identity_bytes)?;
+
+ Ok(Self {
+ target_port_identity,
+ })
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +
use crate::datastructures::{common::WireTimestamp, WireFormat, WireFormatError};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct SyncMessage {
+ pub(crate) origin_timestamp: WireTimestamp,
+}
+
+impl SyncMessage {
+ pub(crate) fn content_size(&self) -> usize {
+ 10
+ }
+
+ pub(crate) fn serialize_content(&self, buffer: &mut [u8]) -> Result<(), WireFormatError> {
+ self.origin_timestamp.serialize(&mut buffer[0..10])?;
+ Ok(())
+ }
+
+ pub(crate) fn deserialize_content(buffer: &[u8]) -> Result<Self, WireFormatError> {
+ match buffer.get(0..10) {
+ None => Err(WireFormatError::BufferTooShort),
+ Some(slice) => Ok(Self {
+ origin_timestamp: WireTimestamp::deserialize(slice)?,
+ }),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn timestamp_wireformat() {
+ let representations = [(
+ [0x00, 0x00, 0x45, 0xb1, 0x11, 0x5a, 0x0a, 0x64, 0xfa, 0xb0],
+ SyncMessage {
+ origin_timestamp: WireTimestamp {
+ seconds: 1169232218,
+ nanos: 174389936,
+ },
+ },
+ )];
+
+ for (byte_representation, object_representation) in representations {
+ // Test the serialization output
+ let mut serialization_buffer = [0; 10];
+ object_representation
+ .serialize_content(&mut serialization_buffer)
+ .unwrap();
+ assert_eq!(serialization_buffer, byte_representation);
+
+ // Test the deserialization output
+ let deserialized_data = SyncMessage::deserialize_content(&byte_representation).unwrap();
+ assert_eq!(deserialized_data, object_representation);
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +
//! General datastructures as defined by the ptp spec
+
+use core::fmt::Debug;
+
+use self::messages::EnumConversionError;
+
+pub mod common;
+pub mod datasets;
+pub mod messages;
+
+#[derive(Clone, Debug)]
+pub(crate) enum WireFormatError {
+ EnumConversionError,
+ BufferTooShort,
+ CapacityError,
+ Invalid,
+}
+
+impl core::fmt::Display for WireFormatError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ WireFormatError::EnumConversionError => f.write_str("enum conversion failed"),
+ WireFormatError::BufferTooShort => f.write_str("a buffer is too short"),
+ WireFormatError::CapacityError => f.write_str("a container has insufficient capacity"),
+ WireFormatError::Invalid => f.write_str("an invariant was violated"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for WireFormatError {}
+
+#[cfg(feature = "error_in_core")]
+impl core::error::Error for WireFormatError {}
+
+impl From<arrayvec::CapacityError> for WireFormatError {
+ fn from(_: arrayvec::CapacityError) -> Self {
+ WireFormatError::CapacityError
+ }
+}
+
+impl From<EnumConversionError> for WireFormatError {
+ fn from(_: EnumConversionError) -> Self {
+ Self::EnumConversionError
+ }
+}
+
+trait WireFormat: Debug + Clone + Eq {
+ /// The byte size on the wire of this object
+ fn wire_size(&self) -> usize;
+
+ /// Serializes the object into the PTP wire format.
+ ///
+ /// Returns the used buffer size that contains the message or an error.
+ fn serialize(&self, buffer: &mut [u8]) -> Result<(), WireFormatError>;
+
+ /// Deserializes the object from the PTP wire format.
+ ///
+ /// Returns the object and the size in the buffer that it takes up or an
+ /// error.
+ fn deserialize(buffer: &[u8]) -> Result<Self, WireFormatError>;
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +
//! Implementation of [BasicFilter]
+
+use fixed::traits::LossyInto;
+
+use super::{Filter, FilterUpdate};
+use crate::{port::Measurement, time::Duration, Clock, Time};
+
+#[derive(Debug)]
+struct PrevStepData {
+ event_time: Time,
+ offset: Duration,
+ correction: Duration,
+}
+
+/// A simple averaging filter
+///
+/// This filter uses simple averaging to determine what the clock control
+/// outputs should be.
+#[derive(Debug)]
+pub struct BasicFilter {
+ last_step: Option<PrevStepData>,
+
+ offset_confidence: Duration,
+ freq_confidence: f64,
+
+ gain: f64,
+
+ cur_freq: f64,
+}
+
+impl Filter for BasicFilter {
+ type Config = f64;
+
+ fn new(gain: f64) -> Self {
+ Self {
+ last_step: None,
+ offset_confidence: Duration::from_nanos(1_000_000_000),
+ freq_confidence: 1e-4,
+ gain,
+ cur_freq: 0.0,
+ }
+ }
+
+ fn measurement<C: Clock>(&mut self, measurement: Measurement, clock: &mut C) -> FilterUpdate {
+ let mut update = FilterUpdate::default();
+
+ if let Some(delay) = measurement.delay {
+ update.mean_delay = Some(delay);
+ }
+
+ let Some(offset) = measurement.offset else {
+ // No measurement, so no further actions
+ return update;
+ };
+
+ // Reset on too-large difference
+ if offset.abs() > Duration::from_nanos(1_000_000_000) {
+ log::debug!("Offset too large, stepping {}", offset);
+ self.offset_confidence = Duration::from_nanos(1_000_000_000);
+ self.freq_confidence = 1e-4;
+
+ if let Err(error) = clock.step_clock(-offset) {
+ log::error!("Could not step clock: {:?}", error);
+ }
+ return update;
+ }
+
+ // Determine offset
+ let mut clamped_offset = offset;
+ if offset.abs() > self.offset_confidence {
+ clamped_offset = offset.clamp(-self.offset_confidence, self.offset_confidence);
+ self.offset_confidence *= 2i32;
+ } else {
+ self.offset_confidence -= (self.offset_confidence - offset.abs()) * self.gain;
+ }
+
+ // And decide it's correction
+ let correction = -clamped_offset * self.gain;
+
+ let freq_corr = if let Some(last_step) = &self.last_step {
+ // Calculate interval for us
+ let interval_local: f64 =
+ (measurement.event_time - last_step.event_time - last_step.correction)
+ .nanos()
+ .lossy_into();
+ // and for the master
+ let interval_master: f64 = ((measurement.event_time - offset)
+ - (last_step.event_time - last_step.offset))
+ .nanos()
+ .lossy_into();
+
+ // get relative frequency difference
+ let mut freq_diff = interval_local / interval_master;
+ if libm::fabs(freq_diff - 1.0) > self.freq_confidence {
+ freq_diff = freq_diff.clamp(1.0 - self.freq_confidence, 1.0 + self.freq_confidence);
+ self.freq_confidence *= 2.0;
+ } else {
+ self.freq_confidence -=
+ (self.freq_confidence - libm::fabs(freq_diff - 1.0)) * self.gain;
+ }
+
+ // and decide the correction (and convert to ppm)
+ -(freq_diff - 1.0) * self.gain * 0.1 * 1e6
+ } else {
+ // No data, so first run, so initialize
+ if let Err(error) = clock.set_frequency(0.0) {
+ log::error!("Could not initialize clock frequency: {:?}", error);
+ }
+ self.cur_freq = 0.0;
+ 0.0
+ };
+
+ // unwrap is ok here since we always have an offset
+ log::info!(
+ "Offset to master: {:e}ns, corrected with phase change {:e}ns and freq change {:e}ppm",
+ offset.nanos(),
+ correction.nanos(),
+ freq_corr
+ );
+
+ // Store data for next time
+ self.last_step = Some(PrevStepData {
+ event_time: measurement.event_time,
+ offset,
+ correction,
+ });
+
+ if let Err(error) = clock.step_clock(correction) {
+ log::error!("Could not step clock: {:?}", error);
+ }
+ if let Err(error) = clock.set_frequency(self.cur_freq + freq_corr) {
+ log::error!("Could not adjust clock frequency: {:?}", error);
+ } else {
+ self.cur_freq += freq_corr;
+ }
+ update
+ }
+
+ fn demobilize<C: Clock>(self, _clock: &mut C) {
+ // ignore
+ }
+
+ fn update<C: Clock>(&mut self, _clock: &mut C) -> FilterUpdate {
+ // ignore
+ Default::default()
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +
//! Definitions and implementations for the abstracted measurement filters
+
+pub mod basic;
+
+use crate::{port::Measurement, Clock, Duration};
+
+#[derive(Debug, Clone, Default, PartialEq, Eq)]
+pub struct FilterUpdate {
+ pub next_update: Option<core::time::Duration>,
+ pub mean_delay: Option<Duration>,
+}
+
+/// A filter for post-processing time measurements.
+///
+/// Filters are responsible for dealing with the network noise, and should
+/// average out the input a bit so minor network variations are not immediately
+/// reflected in the synchronization of the clock.
+///
+/// This crate provides a simple [`BasicFilter`](basic::BasicFilter) which is
+/// suitable for most needs, but users can implement their own if desired.
+pub trait Filter {
+ type Config: Clone;
+
+ /// Create a new instance of the filter.
+ fn new(config: Self::Config) -> Self;
+
+ /// Put a new measurement in the filter.
+ /// The filter can then use this to adjust the clock
+ fn measurement<C: Clock>(&mut self, m: Measurement, clock: &mut C) -> FilterUpdate;
+
+ /// Update initiated through [FilterUpdate::next_update] timeout.
+ fn update<C: Clock>(&mut self, clock: &mut C) -> FilterUpdate;
+
+ /// Handle ending of time synchronization from the source
+ /// associated with this filter.
+ fn demobilize<C: Clock>(self, clock: &mut C);
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +
//! Statime is a library providing an implementation of PTP version 2.1
+//! (IEEE1588-2019). It provides all the building blocks to setup PTP ordinary
+//! and boundary clocks.
+//!
+//! Note: We are currently planning a major overhaul of the library. This will
+//! also result in significant changes to the public API.
+//!
+//! # Device interfaces
+//! `statime` is designed to be able to work with many different underlying
+//! platforms, including embedded targets. This does mean that it cannot use the
+//! standard library and platform specific libraries to interact with the system
+//! clock and to access the network. That needs to be provided by the user of
+//! the library.
+//!
+//! The `statime` crate defines a [`Clock`] interface that provide access to the
+//! system clock. The [`NetworkRuntime`] and [`NetworkPort`]
+//! abstractions provide the needed glue to access the network.
+//!
+//! On modern linux kernels, the `statime-linux` crate provides ready to use
+//! implementations of these interfaces. For other platforms the user will need
+//! to implement these themselves.
+//!
+//! # Clock identities
+//!
+//! All ptp clocks in a network need a unique clock identity. One way to achieve
+//! this is to use (one of) the device's mac address to generate this
+//! identifier. As this requires platform specific code to get the mac address,
+//! this library does not implement this. Rather, direct access is given to the
+//! clock identity type, and the user can create one from a mac address by
+//! storing it in the first six bytes of the clock identifier, setting the
+//! remaining bytes to 0. For more details on the exact specification of the
+//! generation procedure, see IEEE1588-2019 section 7.5.2.2.2
+
+#![no_std]
+
+#[cfg(feature = "std")]
+extern crate std;
+
+mod bmc;
+mod clock;
+mod config;
+mod datastructures;
+mod filters;
+mod port;
+mod ptp_instance;
+mod time;
+
+pub use clock::Clock;
+pub use config::{DelayMechanism, InstanceConfig, PortConfig};
+#[cfg(feature = "fuzz")]
+pub use datastructures::messages::FuzzMessage;
+pub use datastructures::{
+ common::{ClockAccuracy, ClockIdentity, ClockQuality, LeapIndicator, TimeSource},
+ datasets::TimePropertiesDS,
+ messages::{SdoId, MAX_DATA_LEN},
+};
+pub use filters::{basic::BasicFilter, Filter};
+pub use port::{
+ InBmca, Measurement, Port, PortAction, PortActionIterator, Running, TimestampContext,
+};
+pub use ptp_instance::PtpInstance;
+pub use time::{Duration, Interval, Time};
+
use crate::time::{Duration, Time};
+
+/// A single measurement as produced by a PTP port.
+/// Depending on what trigerred the measurements, not
+/// all fields will be populated
+#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
+pub struct Measurement {
+ /// Time this measurement was made.
+ pub event_time: Time,
+ /// Offset to the remote PTP node.
+ pub offset: Option<Duration>,
+ /// Delay to the remote PTP node.
+ pub delay: Option<Duration>,
+ /// Raw offset calculated from a sync message
+ pub raw_sync_offset: Option<Duration>,
+ /// Raw offset calculated from a delay message
+ pub raw_delay_offset: Option<Duration>,
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +
use core::ops::Deref;
+
+use arrayvec::ArrayVec;
+use atomic_refcell::{AtomicRef, AtomicRefCell};
+pub use measurement::Measurement;
+use rand::Rng;
+use state::{MasterState, PortState};
+
+use self::state::SlaveState;
+use crate::{
+ bmc::{
+ acceptable_master::AcceptableMasterList,
+ bmca::{BestAnnounceMessage, Bmca, RecommendedState},
+ },
+ clock::Clock,
+ config::PortConfig,
+ datastructures::{
+ common::{LeapIndicator, PortIdentity, TimeSource},
+ datasets::{CurrentDS, DefaultDS, ParentDS, TimePropertiesDS},
+ messages::{Message, MessageBody},
+ },
+ filters::{Filter, FilterUpdate},
+ ptp_instance::PtpInstanceState,
+ time::Duration,
+ Time, MAX_DATA_LEN,
+};
+
+// Needs to be here because of use rules
+macro_rules! actions {
+ [] => {
+ {
+ crate::port::PortActionIterator::from(::arrayvec::ArrayVec::new())
+ }
+ };
+ [$action:expr] => {
+ {
+ let mut list = ::arrayvec::ArrayVec::new();
+ list.push($action);
+ PortActionIterator::from(list)
+ }
+ };
+ [$action1:expr, $action2:expr] => {
+ {
+ let mut list = ::arrayvec::ArrayVec::new();
+ list.push($action1);
+ list.push($action2);
+ PortActionIterator::from(list)
+ }
+ };
+}
+
+mod measurement;
+mod sequence_id;
+pub(crate) mod state;
+
+/// A single port of the PTP instance
+///
+/// One of these needs to be created per port of the PTP instance.
+#[derive(Debug)]
+pub struct Port<L, A, R, C, F: Filter> {
+ config: PortConfig<()>,
+ filter_config: F::Config,
+ clock: C,
+ // PortDS port_identity
+ pub(crate) port_identity: PortIdentity,
+ // Corresponds with PortDS port_state and enabled
+ port_state: PortState<F>,
+ bmca: Bmca<A>,
+ packet_buffer: [u8; MAX_DATA_LEN],
+ lifecycle: L,
+ rng: R,
+}
+
+#[derive(Debug)]
+pub struct Running<'a> {
+ state_refcell: &'a AtomicRefCell<PtpInstanceState>,
+ state: AtomicRef<'a, PtpInstanceState>,
+}
+
+#[derive(Debug)]
+pub struct InBmca<'a> {
+ pending_action: PortActionIterator<'static>,
+ local_best: Option<BestAnnounceMessage>,
+ state_refcell: &'a AtomicRefCell<PtpInstanceState>,
+}
+
+// Making this non-copy and non-clone ensures a single handle_send_timestamp
+// per SendTimeCritical
+#[derive(Debug)]
+pub struct TimestampContext {
+ inner: TimestampContextInner,
+}
+
+#[derive(Debug)]
+enum TimestampContextInner {
+ Sync { id: u16 },
+ DelayReq { id: u16 },
+}
+
+#[derive(Debug)]
+pub enum PortAction<'a> {
+ SendTimeCritical {
+ context: TimestampContext,
+ data: &'a [u8],
+ },
+ SendGeneral {
+ data: &'a [u8],
+ },
+ ResetAnnounceTimer {
+ duration: core::time::Duration,
+ },
+ ResetSyncTimer {
+ duration: core::time::Duration,
+ },
+ ResetDelayRequestTimer {
+ duration: core::time::Duration,
+ },
+ ResetAnnounceReceiptTimer {
+ duration: core::time::Duration,
+ },
+ ResetFilterUpdateTimer {
+ duration: core::time::Duration,
+ },
+}
+
+const MAX_ACTIONS: usize = 2;
+
+/// Guarantees to end user: Any set of actions will only ever contain a single
+/// time critical send
+#[derive(Debug)]
+#[must_use]
+pub struct PortActionIterator<'a> {
+ internal: <ArrayVec<PortAction<'a>, MAX_ACTIONS> as IntoIterator>::IntoIter,
+}
+
+impl<'a> PortActionIterator<'a> {
+ pub fn empty() -> Self {
+ Self {
+ internal: ArrayVec::new().into_iter(),
+ }
+ }
+ fn from(list: ArrayVec<PortAction<'a>, MAX_ACTIONS>) -> Self {
+ Self {
+ internal: list.into_iter(),
+ }
+ }
+ fn from_filter(update: FilterUpdate) -> Self {
+ if let Some(duration) = update.next_update {
+ actions![PortAction::ResetFilterUpdateTimer { duration }]
+ } else {
+ actions![]
+ }
+ }
+}
+
+impl<'a> Iterator for PortActionIterator<'a> {
+ type Item = PortAction<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.internal.next()
+ }
+}
+
+impl<'a, A, C: Clock, F: Filter, R: Rng> Port<Running<'a>, A, R, C, F> {
+ // Send timestamp for last timecritical message became available
+ pub fn handle_send_timestamp(
+ &mut self,
+ context: TimestampContext,
+ timestamp: Time,
+ ) -> PortActionIterator<'_> {
+ let actions = self.port_state.handle_timestamp(
+ context,
+ timestamp,
+ self.port_identity,
+ &self.lifecycle.state.default_ds,
+ &mut self.clock,
+ &mut self.packet_buffer,
+ );
+
+ actions
+ }
+
+ // Handle the announce timer going of
+ pub fn handle_announce_timer(&mut self) -> PortActionIterator<'_> {
+ self.port_state.send_announce(
+ self.lifecycle.state.deref(),
+ &self.config,
+ self.port_identity,
+ &mut self.packet_buffer,
+ )
+ }
+
+ // Handle the sync timer going of
+ pub fn handle_sync_timer(&mut self) -> PortActionIterator<'_> {
+ self.port_state.send_sync(
+ &self.config,
+ self.port_identity,
+ &self.lifecycle.state.default_ds,
+ &mut self.packet_buffer,
+ )
+ }
+
+ // Handle the sync timer going of
+ pub fn handle_delay_request_timer(&mut self) -> PortActionIterator<'_> {
+ self.port_state.send_delay_request(
+ &mut self.rng,
+ &self.config,
+ self.port_identity,
+ &self.lifecycle.state.default_ds,
+ &mut self.packet_buffer,
+ )
+ }
+
+ // Handle the announce receipt timer going off
+ pub fn handle_announce_receipt_timer(&mut self) -> PortActionIterator<'_> {
+ // we didn't hear announce messages from other masters, so become master
+ // ourselves
+ match self.port_state {
+ PortState::Master(_) => (),
+ _ => self.set_forced_port_state(PortState::Master(MasterState::new())),
+ }
+
+ // Immediately start sending syncs and announces
+ actions![
+ PortAction::ResetAnnounceTimer {
+ duration: core::time::Duration::from_secs(0)
+ },
+ PortAction::ResetSyncTimer {
+ duration: core::time::Duration::from_secs(0)
+ }
+ ]
+ }
+
+ pub fn handle_filter_update_timer(&mut self) -> PortActionIterator {
+ self.port_state.handle_filter_update(&mut self.clock)
+ }
+
+ // Start a BMCA cycle and ensure this happens instantly from the perspective of
+ // the port
+ pub fn start_bmca(self) -> Port<InBmca<'a>, A, R, C, F> {
+ Port {
+ port_state: self.port_state,
+ config: self.config,
+ filter_config: self.filter_config,
+ clock: self.clock,
+ port_identity: self.port_identity,
+ bmca: self.bmca,
+ rng: self.rng,
+ packet_buffer: [0; MAX_DATA_LEN],
+ lifecycle: InBmca {
+ pending_action: actions![],
+ local_best: None,
+ state_refcell: self.lifecycle.state_refcell,
+ },
+ }
+ }
+}
+
+impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng> Port<Running<'a>, A, R, C, F> {
+ // Handle a message over the timecritical channel
+ pub fn handle_timecritical_receive(
+ &mut self,
+ data: &[u8],
+ timestamp: Time,
+ ) -> PortActionIterator {
+ let message = match Message::deserialize(data) {
+ Ok(message) => message,
+ Err(error) => {
+ log::warn!("Could not parse packet: {:?}", error);
+ return actions![];
+ }
+ };
+
+ // Only process messages from the same domain
+ if message.header().sdo_id != self.lifecycle.state.default_ds.sdo_id
+ || message.header().domain_number != self.lifecycle.state.default_ds.domain_number
+ {
+ return actions![];
+ }
+
+ if message.is_event() {
+ self.port_state.handle_event_receive(
+ message,
+ timestamp,
+ self.config.min_delay_req_interval(),
+ self.port_identity,
+ &mut self.clock,
+ &mut self.packet_buffer,
+ )
+ } else {
+ self.handle_general_internal(message)
+ }
+ }
+
+ // Handle a general ptp message
+ pub fn handle_general_receive(&mut self, data: &[u8]) -> PortActionIterator {
+ let message = match Message::deserialize(data) {
+ Ok(message) => message,
+ Err(error) => {
+ log::warn!("Could not parse packet: {:?}", error);
+ return actions![];
+ }
+ };
+
+ // Only process messages from the same domain
+ if message.header().sdo_id != self.lifecycle.state.default_ds.sdo_id
+ || message.header().domain_number != self.lifecycle.state.default_ds.domain_number
+ {
+ return actions![];
+ }
+
+ self.handle_general_internal(message)
+ }
+
+ fn handle_general_internal(&mut self, message: Message<'_>) -> PortActionIterator<'_> {
+ match message.body {
+ MessageBody::Announce(announce) => {
+ self.bmca
+ .register_announce_message(&message.header, &announce);
+ actions![PortAction::ResetAnnounceReceiptTimer {
+ duration: self.config.announce_duration(&mut self.rng),
+ }]
+ }
+ _ => {
+ self.port_state
+ .handle_general_receive(message, self.port_identity, &mut self.clock)
+ }
+ }
+ }
+}
+
+impl<'a, A, C, F: Filter, R> Port<InBmca<'a>, A, R, C, F> {
+ // End a BMCA cycle and make the port available again
+ pub fn end_bmca(self) -> (Port<Running<'a>, A, R, C, F>, PortActionIterator<'static>) {
+ (
+ Port {
+ port_state: self.port_state,
+ config: self.config,
+ filter_config: self.filter_config,
+ clock: self.clock,
+ port_identity: self.port_identity,
+ bmca: self.bmca,
+ rng: self.rng,
+ packet_buffer: [0; MAX_DATA_LEN],
+ lifecycle: Running {
+ state_refcell: self.lifecycle.state_refcell,
+ state: self.lifecycle.state_refcell.borrow(),
+ },
+ },
+ self.lifecycle.pending_action,
+ )
+ }
+}
+
+impl<L, A, R, C: Clock, F: Filter> Port<L, A, R, C, F> {
+ fn set_forced_port_state(&mut self, mut state: PortState<F>) {
+ log::info!(
+ "new state for port {}: {} -> {}",
+ self.port_identity.port_number,
+ self.port_state,
+ state
+ );
+ core::mem::swap(&mut self.port_state, &mut state);
+ state.demobilize_filter(&mut self.clock);
+ }
+}
+
+impl<L, A, R, C, F: Filter> Port<L, A, R, C, F> {
+ pub fn is_steering(&self) -> bool {
+ matches!(self.port_state, PortState::Slave(_))
+ }
+
+ pub(crate) fn state(&self) -> &PortState<F> {
+ &self.port_state
+ }
+
+ pub(crate) fn number(&self) -> u16 {
+ self.port_identity.port_number
+ }
+}
+
+impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng> Port<InBmca<'a>, A, R, C, F> {
+ pub(crate) fn calculate_best_local_announce_message(&mut self) {
+ self.lifecycle.local_best = self.bmca.take_best_port_announce_message()
+ }
+}
+
+impl<'a, A, C: Clock, F: Filter, R: Rng> Port<InBmca<'a>, A, R, C, F> {
+ pub(crate) fn step_announce_age(&mut self, step: Duration) {
+ self.bmca.step_age(step);
+ }
+
+ pub(crate) fn best_local_announce_message(&self) -> Option<BestAnnounceMessage> {
+ // Announce messages received on a masterOnly PTP Port shall not be considered
+ // in the operation of the best master clock algorithm or in the update
+ // of data sets.
+ if self.config.master_only {
+ None
+ } else {
+ self.lifecycle.local_best
+ }
+ }
+
+ pub(crate) fn set_recommended_state(
+ &mut self,
+ recommended_state: RecommendedState,
+ time_properties_ds: &mut TimePropertiesDS,
+ current_ds: &mut CurrentDS,
+ parent_ds: &mut ParentDS,
+ default_ds: &DefaultDS,
+ ) {
+ self.set_recommended_port_state(&recommended_state, default_ds);
+
+ match recommended_state {
+ RecommendedState::M1(defaultds) | RecommendedState::M2(defaultds) => {
+ // a slave-only PTP port should never end up in the master state
+ debug_assert!(!default_ds.slave_only);
+
+ current_ds.steps_removed = 0;
+ current_ds.offset_from_master = Duration::ZERO;
+ current_ds.mean_delay = Duration::ZERO;
+
+ parent_ds.parent_port_identity.clock_identity = defaultds.clock_identity;
+ parent_ds.parent_port_identity.port_number = 0;
+ parent_ds.grandmaster_identity = defaultds.clock_identity;
+ parent_ds.grandmaster_clock_quality = defaultds.clock_quality;
+ parent_ds.grandmaster_priority_1 = defaultds.priority_1;
+ parent_ds.grandmaster_priority_2 = defaultds.priority_2;
+
+ time_properties_ds.leap_indicator = LeapIndicator::NoLeap;
+ time_properties_ds.current_utc_offset = None;
+ time_properties_ds.ptp_timescale = true;
+ time_properties_ds.time_traceable = false;
+ time_properties_ds.frequency_traceable = false;
+ time_properties_ds.time_source = TimeSource::InternalOscillator;
+ }
+ RecommendedState::M3(_) | RecommendedState::P1(_) | RecommendedState::P2(_) => {}
+ RecommendedState::S1(announce_message) => {
+ // a master-only PTP port should never end up in the slave state
+ debug_assert!(!self.config.master_only);
+
+ current_ds.steps_removed = announce_message.steps_removed + 1;
+
+ parent_ds.parent_port_identity = announce_message.header.source_port_identity;
+ parent_ds.grandmaster_identity = announce_message.grandmaster_identity;
+ parent_ds.grandmaster_clock_quality = announce_message.grandmaster_clock_quality;
+ parent_ds.grandmaster_priority_1 = announce_message.grandmaster_priority_1;
+ parent_ds.grandmaster_priority_2 = announce_message.grandmaster_priority_2;
+
+ *time_properties_ds = announce_message.time_properties();
+
+ if let Err(error) = self.clock.set_properties(time_properties_ds) {
+ log::error!("Could not update clock: {:?}", error);
+ }
+ }
+ }
+
+ // TODO: Discuss if we should change the clock's own time properties, or keep
+ // the master's time properties separately
+ if let RecommendedState::S1(announce_message) = &recommended_state {
+ // Update time properties
+ *time_properties_ds = announce_message.time_properties();
+ }
+ }
+
+ fn set_recommended_port_state(
+ &mut self,
+ recommended_state: &RecommendedState,
+ default_ds: &DefaultDS,
+ ) {
+ match recommended_state {
+ // TODO set things like steps_removed once they are added
+ // TODO make sure states are complete
+ RecommendedState::S1(announce_message) => {
+ // a master-only PTP port should never end up in the slave state
+ debug_assert!(!self.config.master_only);
+
+ let remote_master = announce_message.header.source_port_identity;
+
+ let update_state = match &self.port_state {
+ PortState::Listening | PortState::Master(_) | PortState::Passive => true,
+ PortState::Slave(old_state) => old_state.remote_master() != remote_master,
+ };
+
+ if update_state {
+ let state = PortState::Slave(SlaveState::new(
+ remote_master,
+ self.filter_config.clone(),
+ ));
+ self.set_forced_port_state(state);
+
+ let duration = self.config.announce_duration(&mut self.rng);
+ let reset_announce = PortAction::ResetAnnounceReceiptTimer { duration };
+ let reset_delay = PortAction::ResetDelayRequestTimer {
+ duration: core::time::Duration::ZERO,
+ };
+ self.lifecycle.pending_action = actions![reset_announce, reset_delay];
+ }
+ }
+ RecommendedState::M1(_) | RecommendedState::M2(_) | RecommendedState::M3(_) => {
+ if default_ds.slave_only {
+ match self.port_state {
+ PortState::Listening => { /* do nothing */ }
+ PortState::Slave(_) | PortState::Passive => {
+ self.set_forced_port_state(PortState::Listening);
+
+ // consistent with Port<InBmca>::new()
+ let duration = self.config.announce_duration(&mut self.rng);
+ let reset_announce = PortAction::ResetAnnounceReceiptTimer { duration };
+ self.lifecycle.pending_action = actions![reset_announce];
+ }
+ PortState::Master(_) => {
+ let msg = "slave-only PTP port should not be in master state";
+ debug_assert!(!default_ds.slave_only, "{msg}");
+ log::error!("{msg}");
+ }
+ }
+ } else {
+ match self.port_state {
+ PortState::Listening | PortState::Slave(_) | PortState::Passive => {
+ self.set_forced_port_state(PortState::Master(MasterState::new()));
+
+ // Immediately start sending announces and syncs
+ let duration = core::time::Duration::from_secs(0);
+ self.lifecycle.pending_action = actions![
+ PortAction::ResetAnnounceTimer { duration },
+ PortAction::ResetSyncTimer { duration }
+ ];
+ }
+ PortState::Master(_) => { /* do nothing */ }
+ }
+ }
+ }
+ RecommendedState::P1(_) | RecommendedState::P2(_) => match self.port_state {
+ PortState::Listening | PortState::Slave(_) | PortState::Master(_) => {
+ self.set_forced_port_state(PortState::Passive)
+ }
+ PortState::Passive => {}
+ },
+ }
+ }
+}
+
+impl<'a, A, C, F: Filter, R: Rng> Port<InBmca<'a>, A, R, C, F> {
+ /// Create a new port from a port dataset on a given interface.
+ pub(crate) fn new(
+ state_refcell: &'a AtomicRefCell<PtpInstanceState>,
+ config: PortConfig<A>,
+ filter_config: F::Config,
+ clock: C,
+ port_identity: PortIdentity,
+ mut rng: R,
+ ) -> Self {
+ let duration = config.announce_duration(&mut rng);
+ let bmca = Bmca::new(
+ config.acceptable_master_list,
+ config.announce_interval.as_duration().into(),
+ port_identity,
+ );
+
+ Port {
+ config: PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: config.delay_mechanism,
+ announce_interval: config.announce_interval,
+ announce_receipt_timeout: config.announce_receipt_timeout,
+ sync_interval: config.sync_interval,
+ master_only: config.master_only,
+ delay_asymmetry: config.delay_asymmetry,
+ },
+ filter_config,
+ clock,
+ port_identity,
+ port_state: PortState::Listening,
+ bmca,
+ rng,
+ packet_buffer: [0; MAX_DATA_LEN],
+ lifecycle: InBmca {
+ pending_action: actions![PortAction::ResetAnnounceReceiptTimer { duration }],
+ local_best: None,
+ state_refcell,
+ },
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ datastructures::messages::{AnnounceMessage, Header, PtpVersion},
+ BasicFilter, DelayMechanism, InstanceConfig, Interval,
+ };
+
+ struct TestClock;
+
+ impl Clock for TestClock {
+ type Error = ();
+
+ fn set_frequency(&mut self, _freq: f64) -> Result<Time, Self::Error> {
+ Ok(Time::default())
+ }
+
+ fn now(&self) -> Time {
+ panic!("Shouldn't be called");
+ }
+
+ fn set_properties(
+ &mut self,
+ _time_properties_ds: &crate::TimePropertiesDS,
+ ) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn step_clock(&mut self, _offset: Duration) -> Result<Time, Self::Error> {
+ Ok(Time::default())
+ }
+ }
+
+ fn default_announce_message_header() -> Header {
+ Header {
+ sdo_id: Default::default(),
+ version: PtpVersion::new(2, 1).unwrap(),
+ domain_number: Default::default(),
+ alternate_master_flag: false,
+ two_step_flag: false,
+ unicast_flag: false,
+ ptp_profile_specific_1: false,
+ ptp_profile_specific_2: false,
+ leap61: false,
+ leap59: false,
+ current_utc_offset_valid: false,
+ ptp_timescale: false,
+ time_tracable: false,
+ frequency_tracable: false,
+ synchronization_uncertain: false,
+ correction_field: Default::default(),
+ source_port_identity: Default::default(),
+ sequence_id: Default::default(),
+ log_message_interval: Default::default(),
+ }
+ }
+
+ fn default_announce_message() -> AnnounceMessage {
+ AnnounceMessage {
+ header: default_announce_message_header(),
+ origin_timestamp: Default::default(),
+ current_utc_offset: Default::default(),
+ grandmaster_priority_1: Default::default(),
+ grandmaster_clock_quality: Default::default(),
+ grandmaster_priority_2: Default::default(),
+ grandmaster_identity: Default::default(),
+ steps_removed: Default::default(),
+ time_source: Default::default(),
+ }
+ }
+
+ #[test]
+ fn test_announce_receive() {
+ let default_ds = DefaultDS::new(InstanceConfig {
+ clock_identity: Default::default(),
+ priority_1: 255,
+ priority_2: 255,
+ domain_number: 0,
+ slave_only: false,
+ sdo_id: Default::default(),
+ });
+
+ let parent_ds = ParentDS::new(default_ds);
+
+ let state = AtomicRefCell::new(PtpInstanceState {
+ default_ds,
+ current_ds: Default::default(),
+ parent_ds,
+ time_properties_ds: Default::default(),
+ });
+
+ let port = Port::<_, _, _, _, BasicFilter>::new(
+ &state,
+ PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: DelayMechanism::E2E {
+ interval: Interval::from_log_2(1),
+ },
+ announce_interval: Interval::from_log_2(1),
+ announce_receipt_timeout: 3,
+ sync_interval: Interval::from_log_2(0),
+ master_only: false,
+ delay_asymmetry: Duration::ZERO,
+ },
+ 0.25,
+ TestClock,
+ Default::default(),
+ rand::rngs::mock::StepRng::new(2, 1),
+ );
+
+ let (mut port, _) = port.end_bmca();
+
+ let mut announce = default_announce_message();
+ announce.header.source_port_identity.clock_identity.0 = [1, 2, 3, 4, 5, 6, 7, 8];
+ let announce_message = Message {
+ header: announce.header,
+ body: MessageBody::Announce(announce),
+ suffix: Default::default(),
+ };
+ let mut packet = [0; MAX_DATA_LEN];
+ let packet_len = announce_message.serialize(&mut packet).unwrap();
+ let packet = &packet[..packet_len];
+
+ let mut actions = port.handle_general_receive(packet);
+ let Some(PortAction::ResetAnnounceReceiptTimer { .. }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let mut actions = port.handle_general_receive(packet);
+ let Some(PortAction::ResetAnnounceReceiptTimer { .. }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let mut actions = port.handle_general_receive(packet);
+ let Some(PortAction::ResetAnnounceReceiptTimer { .. }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let mut port = port.start_bmca();
+ port.calculate_best_local_announce_message();
+ assert!(port.best_local_announce_message().is_some());
+ }
+
+ #[test]
+ fn test_announce_receive_via_timecritical() {
+ let default_ds = DefaultDS::new(InstanceConfig {
+ clock_identity: Default::default(),
+ priority_1: 255,
+ priority_2: 255,
+ domain_number: 0,
+ slave_only: false,
+ sdo_id: Default::default(),
+ });
+
+ let parent_ds = ParentDS::new(default_ds);
+
+ let state = AtomicRefCell::new(PtpInstanceState {
+ default_ds,
+ current_ds: Default::default(),
+ parent_ds,
+ time_properties_ds: Default::default(),
+ });
+
+ let port = Port::<_, _, _, _, BasicFilter>::new(
+ &state,
+ PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: DelayMechanism::E2E {
+ interval: Interval::from_log_2(1),
+ },
+ announce_interval: Interval::from_log_2(1),
+ announce_receipt_timeout: 3,
+ sync_interval: Interval::from_log_2(0),
+ master_only: false,
+ delay_asymmetry: Duration::ZERO,
+ },
+ 0.25,
+ TestClock,
+ Default::default(),
+ rand::rngs::mock::StepRng::new(2, 1),
+ );
+
+ let (mut port, _) = port.end_bmca();
+
+ let mut announce = default_announce_message();
+ announce.header.source_port_identity.clock_identity.0 = [1, 2, 3, 4, 5, 6, 7, 8];
+ let announce_message = Message {
+ header: announce.header,
+ body: MessageBody::Announce(announce),
+ suffix: Default::default(),
+ };
+ let mut packet = [0; MAX_DATA_LEN];
+ let packet_len = announce_message.serialize(&mut packet).unwrap();
+ let packet = &packet[..packet_len];
+
+ let mut actions = port.handle_timecritical_receive(packet, Time::from_micros(1));
+ let Some(PortAction::ResetAnnounceReceiptTimer { .. }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let mut actions = port.handle_timecritical_receive(packet, Time::from_micros(2));
+ let Some(PortAction::ResetAnnounceReceiptTimer { .. }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let mut actions = port.handle_timecritical_receive(packet, Time::from_micros(3));
+ let Some(PortAction::ResetAnnounceReceiptTimer { .. }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let mut port = port.start_bmca();
+ port.calculate_best_local_announce_message();
+ assert!(port.best_local_announce_message().is_some());
+ }
+}
+
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub(crate) struct SequenceIdGenerator {
+ current: u16,
+}
+
+impl SequenceIdGenerator {
+ pub(crate) fn new() -> Self {
+ SequenceIdGenerator { current: 0 }
+ }
+
+ pub(crate) fn generate(&mut self) -> u16 {
+ let id = self.current;
+ self.current = self.current.wrapping_add(1);
+ id
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +
use core::fmt::Debug;
+
+use crate::{
+ datastructures::{
+ common::PortIdentity,
+ datasets::DefaultDS,
+ messages::{DelayReqMessage, Header, Message, MessageBody},
+ },
+ port::{
+ sequence_id::SequenceIdGenerator, PortAction, PortActionIterator, TimestampContext,
+ TimestampContextInner,
+ },
+ ptp_instance::PtpInstanceState,
+ time::{Interval, Time},
+ PortConfig,
+};
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub(crate) struct MasterState {
+ pub(in crate::port) announce_seq_ids: SequenceIdGenerator,
+ pub(in crate::port) sync_seq_ids: SequenceIdGenerator,
+}
+
+impl MasterState {
+ pub(crate) fn new() -> Self {
+ MasterState {
+ announce_seq_ids: SequenceIdGenerator::new(),
+ sync_seq_ids: SequenceIdGenerator::new(),
+ }
+ }
+
+ pub(crate) fn handle_timestamp<'a>(
+ &mut self,
+ context: TimestampContext,
+ timestamp: Time,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ match context.inner {
+ TimestampContextInner::Sync { id } => {
+ self.handle_sync_timestamp(id, timestamp, port_identity, default_ds, buffer)
+ }
+ _ => {
+ log::error!("Unexpected send timestamp");
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn handle_sync_timestamp<'a>(
+ &mut self,
+ id: u16,
+ timestamp: Time,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ let packet_length =
+ match Message::follow_up(default_ds, port_identity, id, timestamp).serialize(buffer) {
+ Ok(length) => length,
+ Err(error) => {
+ log::error!(
+ "Statime bug: Could not serialize sync follow up {:?}",
+ error
+ );
+ return actions![];
+ }
+ };
+
+ actions![PortAction::SendGeneral {
+ data: &buffer[..packet_length],
+ }]
+ }
+
+ pub(crate) fn send_sync<'a>(
+ &mut self,
+ config: &PortConfig<()>,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ log::trace!("sending sync message");
+
+ let seq_id = self.sync_seq_ids.generate();
+ let packet_length = match Message::sync(default_ds, port_identity, seq_id).serialize(buffer)
+ {
+ Ok(message) => message,
+ Err(error) => {
+ log::error!("Statime bug: Could not serialize sync: {:?}", error);
+ return actions![];
+ }
+ };
+
+ actions![
+ PortAction::ResetSyncTimer {
+ duration: config.sync_interval.as_core_duration(),
+ },
+ PortAction::SendTimeCritical {
+ context: TimestampContext {
+ inner: TimestampContextInner::Sync { id: seq_id },
+ },
+ data: &buffer[..packet_length],
+ }
+ ]
+ }
+
+ pub(crate) fn send_announce<'a>(
+ &mut self,
+ global: &PtpInstanceState,
+ config: &PortConfig<()>,
+ port_identity: PortIdentity,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ log::trace!("sending announce message");
+
+ let packet_length =
+ match Message::announce(global, port_identity, self.announce_seq_ids.generate())
+ .serialize(buffer)
+ {
+ Ok(length) => length,
+ Err(error) => {
+ log::error!(
+ "Statime bug: Could not serialize announce message {:?}",
+ error
+ );
+ return actions![];
+ }
+ };
+
+ actions![
+ PortAction::ResetAnnounceTimer {
+ duration: config.announce_interval.as_core_duration(),
+ },
+ PortAction::SendGeneral {
+ data: &buffer[..packet_length]
+ }
+ ]
+ }
+
+ pub(crate) fn handle_event_receive<'a>(
+ &mut self,
+ message: Message,
+ timestamp: Time,
+ min_delay_req_interval: Interval,
+ port_identity: PortIdentity,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ let header = message.header;
+
+ if header.source_port_identity == port_identity {
+ return actions![];
+ }
+
+ match message.body {
+ MessageBody::DelayReq(message) => self.handle_delay_req(
+ header,
+ message,
+ timestamp,
+ min_delay_req_interval,
+ port_identity,
+ buffer,
+ ),
+ _ => {
+ log::warn!("Unexpected message {:?}", message);
+ actions![]
+ }
+ }
+ }
+
+ fn handle_delay_req<'a>(
+ &mut self,
+ header: Header,
+ message: DelayReqMessage,
+ timestamp: Time,
+ min_delay_req_interval: Interval,
+ port_identity: PortIdentity,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ log::debug!("Received DelayReq");
+ let delay_resp_message = Message::delay_resp(
+ header,
+ message,
+ port_identity,
+ min_delay_req_interval,
+ timestamp,
+ );
+
+ let packet_length = match delay_resp_message.serialize(buffer) {
+ Ok(length) => length,
+ Err(error) => {
+ log::error!("Could not serialize delay response: {:?}", error);
+ return actions![];
+ }
+ };
+
+ actions![PortAction::SendGeneral {
+ data: &buffer[..packet_length],
+ }]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use fixed::types::{I48F16, U96F32};
+
+ use super::*;
+ use crate::{
+ config::InstanceConfig,
+ datastructures::{
+ common::{ClockIdentity, TimeInterval, TlvSet},
+ datasets::{CurrentDS, ParentDS},
+ messages::{Header, SdoId},
+ },
+ time::Interval,
+ Duration, TimePropertiesDS, MAX_DATA_LEN,
+ };
+
+ #[test]
+ fn test_delay_response() {
+ let mut state = MasterState::new();
+
+ let mut buffer = [0u8; MAX_DATA_LEN];
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ sequence_id: 5123,
+ source_port_identity: PortIdentity {
+ port_number: 83,
+ ..Default::default()
+ },
+ correction_field: TimeInterval(I48F16::from_bits(400)),
+ ..Default::default()
+ },
+ body: MessageBody::DelayReq(DelayReqMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_fixed_nanos(U96F32::from_bits((200000 << 32) + (500 << 16))),
+ Interval::from_log_2(2),
+ PortIdentity::default(),
+ &mut buffer,
+ );
+
+ let Some(PortAction::SendGeneral { data }) = action.next() else {
+ panic!("Unexpected resulting action");
+ };
+ assert!(action.next().is_none());
+ drop(action);
+
+ let msg = Message::deserialize(data).unwrap();
+ let msg_header = msg.header;
+
+ let msg = match msg.body {
+ MessageBody::DelayResp(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ assert_eq!(
+ msg.requesting_port_identity,
+ PortIdentity {
+ port_number: 83,
+ ..Default::default()
+ }
+ );
+ assert_eq!(msg_header.sequence_id, 5123);
+ assert_eq!(msg.receive_timestamp, Time::from_micros(200).into());
+ assert_eq!(msg_header.log_message_interval, 2);
+ assert_eq!(
+ msg_header.correction_field,
+ TimeInterval(I48F16::from_bits(900))
+ );
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ sequence_id: 879,
+ source_port_identity: PortIdentity {
+ port_number: 12,
+ ..Default::default()
+ },
+ correction_field: TimeInterval(I48F16::from_bits(200)),
+ ..Default::default()
+ },
+ body: MessageBody::DelayReq(DelayReqMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_fixed_nanos(U96F32::from_bits((220000 << 32) + (300 << 16))),
+ Interval::from_log_2(5),
+ PortIdentity::default(),
+ &mut buffer,
+ );
+
+ let Some(PortAction::SendGeneral { data }) = action.next() else {
+ panic!("Unexpected resulting action");
+ };
+ assert!(action.next().is_none());
+
+ let msg = Message::deserialize(data).unwrap();
+ let msg_header = msg.header;
+
+ let msg = match msg.body {
+ MessageBody::DelayResp(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ assert_eq!(
+ msg.requesting_port_identity,
+ PortIdentity {
+ port_number: 12,
+ ..Default::default()
+ }
+ );
+ assert_eq!(msg_header.sequence_id, 879);
+ assert_eq!(msg.receive_timestamp, Time::from_micros(220).into());
+ assert_eq!(msg_header.log_message_interval, 5);
+ assert_eq!(
+ msg_header.correction_field,
+ TimeInterval(I48F16::from_bits(500))
+ );
+ }
+
+ #[test]
+ fn test_announce() {
+ let mut buffer = [0u8; MAX_DATA_LEN];
+
+ let default_ds = DefaultDS::new(InstanceConfig {
+ clock_identity: ClockIdentity::default(),
+ priority_1: 15,
+ priority_2: 128,
+ domain_number: 0,
+ slave_only: false,
+ sdo_id: SdoId::default(),
+ });
+ let mut parent_ds = ParentDS::new(default_ds);
+ parent_ds.grandmaster_priority_1 = 15;
+ let current_ds = CurrentDS::default();
+ let time_properties_ds = TimePropertiesDS::default();
+ let global = PtpInstanceState {
+ default_ds,
+ current_ds,
+ parent_ds,
+ time_properties_ds,
+ };
+
+ let config = PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: crate::DelayMechanism::E2E {
+ interval: Interval::TWO_SECONDS,
+ },
+ announce_interval: Interval::TWO_SECONDS,
+ announce_receipt_timeout: 2,
+ sync_interval: Interval::ONE_SECOND,
+ master_only: false,
+ delay_asymmetry: Duration::ZERO,
+ };
+ let mut state = MasterState::new();
+
+ let mut actions =
+ state.send_announce(&global, &config, PortIdentity::default(), &mut buffer);
+
+ assert!(matches!(
+ actions.next(),
+ Some(PortAction::ResetAnnounceTimer { .. })
+ ));
+ let Some(PortAction::SendGeneral { data }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let msg = Message::deserialize(data).unwrap();
+ let msg_header = msg.header;
+
+ let msg = match msg.body {
+ MessageBody::Announce(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ assert_eq!(msg.grandmaster_priority_1, 15);
+
+ let mut actions =
+ state.send_announce(&global, &config, PortIdentity::default(), &mut buffer);
+
+ assert!(matches!(
+ actions.next(),
+ Some(PortAction::ResetAnnounceTimer { .. })
+ ));
+ let Some(PortAction::SendGeneral { data }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+
+ let msg2 = Message::deserialize(data).unwrap();
+ let msg2_header = msg2.header;
+
+ let msg2 = match msg2.body {
+ MessageBody::Announce(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ assert_eq!(msg2.grandmaster_priority_1, 15);
+ assert_ne!(msg2_header.sequence_id, msg_header.sequence_id);
+ }
+
+ #[test]
+ fn test_sync() {
+ let mut buffer = [0u8; MAX_DATA_LEN];
+ let config = PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: crate::DelayMechanism::E2E {
+ interval: Interval::TWO_SECONDS,
+ },
+ announce_interval: Interval::TWO_SECONDS,
+ announce_receipt_timeout: 2,
+ sync_interval: Interval::ONE_SECOND,
+ master_only: false,
+ delay_asymmetry: crate::Duration::ZERO,
+ };
+
+ let mut state = MasterState::new();
+ let defaultds = DefaultDS::new(InstanceConfig {
+ clock_identity: ClockIdentity::default(),
+ priority_1: 15,
+ priority_2: 128,
+ domain_number: 0,
+ slave_only: false,
+ sdo_id: SdoId::default(),
+ });
+
+ let mut actions =
+ state.send_sync(&config, PortIdentity::default(), &defaultds, &mut buffer);
+
+ assert!(matches!(
+ actions.next(),
+ Some(PortAction::ResetSyncTimer { .. })
+ ));
+ let Some(PortAction::SendTimeCritical { context, data }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let sync = Message::deserialize(data).unwrap();
+ let sync_header = sync.header;
+
+ let _sync = match sync.body {
+ MessageBody::Sync(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ let mut actions = state.handle_timestamp(
+ context,
+ Time::from_fixed_nanos(U96F32::from_bits((601300 << 32) + (230 << 16))),
+ PortIdentity::default(),
+ &defaultds,
+ &mut buffer,
+ );
+
+ let Some(PortAction::SendGeneral { data }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let follow = Message::deserialize(data).unwrap();
+ let follow_header = follow.header;
+
+ let follow = match follow.body {
+ MessageBody::FollowUp(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ assert_eq!(sync_header.sequence_id, follow_header.sequence_id);
+ assert_eq!(
+ sync_header.correction_field,
+ TimeInterval(I48F16::from_bits(0))
+ );
+ assert_eq!(
+ follow.precise_origin_timestamp,
+ Time::from_fixed_nanos(601300).into()
+ );
+ assert_eq!(
+ follow_header.correction_field,
+ TimeInterval(I48F16::from_bits(230))
+ );
+
+ let mut actions =
+ state.send_sync(&config, PortIdentity::default(), &defaultds, &mut buffer);
+
+ assert!(matches!(
+ actions.next(),
+ Some(PortAction::ResetSyncTimer { .. })
+ ));
+ let Some(PortAction::SendTimeCritical { context, data }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+ drop(actions);
+
+ let sync2 = Message::deserialize(data).unwrap();
+ let sync2_header = sync2.header;
+
+ let _sync2 = match sync2.body {
+ MessageBody::Sync(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ let mut actions = state.handle_timestamp(
+ context,
+ Time::from_fixed_nanos(U96F32::from_bits((1000601300 << 32) + (543 << 16))),
+ PortIdentity::default(),
+ &defaultds,
+ &mut buffer,
+ );
+
+ let Some(PortAction::SendGeneral { data }) = actions.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(actions.next().is_none());
+
+ let follow2 = Message::deserialize(data).unwrap();
+ let follow2_header = follow2.header;
+
+ let follow2 = match follow2.body {
+ MessageBody::FollowUp(msg) => msg,
+ _ => panic!("Unexpected message type"),
+ };
+
+ assert_ne!(sync_header.sequence_id, sync2_header.sequence_id);
+ assert_eq!(sync2_header.sequence_id, follow2_header.sequence_id);
+ assert_eq!(
+ sync2_header.correction_field,
+ TimeInterval(I48F16::from_bits(0))
+ );
+ assert_eq!(
+ follow2.precise_origin_timestamp,
+ Time::from_fixed_nanos(1000601300).into()
+ );
+ assert_eq!(
+ follow2_header.correction_field,
+ TimeInterval(I48F16::from_bits(543))
+ );
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +
use core::fmt::{Display, Formatter};
+
+use rand::Rng;
+
+use super::{PortActionIterator, TimestampContext};
+use crate::{
+ datastructures::{common::PortIdentity, datasets::DefaultDS, messages::Message},
+ ptp_instance::PtpInstanceState,
+ time::{Interval, Time},
+ Clock, Filter, PortConfig,
+};
+
+mod master;
+mod slave;
+
+pub(crate) use master::MasterState;
+pub(crate) use slave::SlaveState;
+
+#[derive(Debug, Default)]
+pub(crate) enum PortState<F> {
+ #[default]
+ Listening,
+ Master(MasterState),
+ Passive,
+ Slave(SlaveState<F>),
+}
+
+impl<F: Filter> PortState<F> {
+ pub(crate) fn handle_timestamp<'a, C: Clock>(
+ &mut self,
+ context: TimestampContext,
+ timestamp: Time,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ clock: &mut C,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ match self {
+ PortState::Slave(slave) => slave.handle_timestamp(context, timestamp, clock),
+ PortState::Master(master) => {
+ master.handle_timestamp(context, timestamp, port_identity, default_ds, buffer)
+ }
+ PortState::Listening | PortState::Passive => actions![],
+ }
+ }
+
+ pub(crate) fn handle_event_receive<'a, C: Clock>(
+ &mut self,
+ message: Message,
+ timestamp: Time,
+ min_delay_req_interval: Interval,
+ port_identity: PortIdentity,
+ clock: &mut C,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ match self {
+ PortState::Master(master) => master.handle_event_receive(
+ message,
+ timestamp,
+ min_delay_req_interval,
+ port_identity,
+ buffer,
+ ),
+ PortState::Slave(slave) => slave.handle_event_receive(message, timestamp, clock),
+ PortState::Listening | PortState::Passive => actions![],
+ }
+ }
+
+ pub(crate) fn handle_general_receive<C: Clock>(
+ &mut self,
+ message: Message,
+ port_identity: PortIdentity,
+ clock: &mut C,
+ ) -> PortActionIterator {
+ match self {
+ PortState::Master(_) => {
+ if message.header().source_port_identity != port_identity {
+ log::warn!("Unexpected message {:?}", message);
+ }
+ actions![]
+ }
+ PortState::Slave(slave) => slave.handle_general_receive(message, port_identity, clock),
+ PortState::Listening | PortState::Passive => {
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn handle_filter_update<C: Clock>(&mut self, clock: &mut C) -> PortActionIterator {
+ match self {
+ PortState::Slave(slave) => slave.handle_filter_update(clock),
+ PortState::Master(_) | PortState::Listening | PortState::Passive => {
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn demobilize_filter<C: Clock>(self, clock: &mut C) {
+ match self {
+ PortState::Slave(slave) => slave.demobilize_filter(clock),
+ PortState::Master(_) | PortState::Listening | PortState::Passive => {}
+ }
+ }
+}
+
+impl<F> PortState<F> {
+ pub(crate) fn send_sync<'a>(
+ &mut self,
+ config: &PortConfig<()>,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ match self {
+ PortState::Master(master) => {
+ master.send_sync(config, port_identity, default_ds, buffer)
+ }
+ PortState::Slave(_) | PortState::Listening | PortState::Passive => {
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn send_delay_request<'a>(
+ &mut self,
+ rng: &mut impl Rng,
+ port_config: &PortConfig<()>,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ match self {
+ PortState::Slave(slave) => {
+ slave.send_delay_request(rng, port_config, port_identity, default_ds, buffer)
+ }
+ PortState::Master(_) | PortState::Listening | PortState::Passive => {
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn send_announce<'a>(
+ &mut self,
+ global: &PtpInstanceState,
+ config: &PortConfig<()>,
+ port_identity: PortIdentity,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ match self {
+ PortState::Master(master) => {
+ master.send_announce(global, config, port_identity, buffer)
+ }
+ PortState::Slave(_) | PortState::Listening | PortState::Passive => actions![],
+ }
+ }
+}
+
+impl<F> Display for PortState<F> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ match self {
+ PortState::Listening => write!(f, "Listening"),
+ PortState::Master(_) => write!(f, "Master"),
+ PortState::Passive => write!(f, "Passive"),
+ PortState::Slave(_) => write!(f, "Slave"),
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +1019 +1020 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +1038 +1039 +1040 +1041 +1042 +1043 +1044 +1045 +1046 +1047 +1048 +1049 +1050 +1051 +1052 +1053 +1054 +1055 +1056 +1057 +1058 +1059 +1060 +1061 +1062 +1063 +1064 +1065 +1066 +1067 +1068 +1069 +1070 +1071 +1072 +1073 +1074 +1075 +1076 +1077 +1078 +1079 +1080 +1081 +1082 +1083 +1084 +1085 +1086 +1087 +1088 +1089 +1090 +1091 +1092 +1093 +1094 +1095 +1096 +1097 +1098 +1099 +1100 +1101 +1102 +1103 +1104 +1105 +1106 +1107 +1108 +1109 +1110 +1111 +1112 +1113 +1114 +1115 +1116 +1117 +1118 +1119 +1120 +1121 +1122 +1123 +1124 +1125 +1126 +1127 +1128 +1129 +1130 +1131 +1132 +1133 +1134 +1135 +1136 +1137 +1138 +1139 +1140 +1141 +1142 +1143 +1144 +1145 +1146 +1147 +1148 +1149 +1150 +1151 +1152 +1153 +1154 +1155 +1156 +1157 +1158 +1159 +1160 +1161 +1162 +1163 +1164 +1165 +1166 +1167 +1168 +1169 +1170 +1171 +1172 +1173 +1174 +1175 +1176 +1177 +1178 +1179 +1180 +1181 +1182 +1183 +1184 +1185 +1186 +1187 +1188 +1189 +1190 +1191 +1192 +1193 +1194 +1195 +1196 +1197 +1198 +1199 +1200 +1201 +
use rand::Rng;
+
+use crate::{
+ datastructures::{
+ common::PortIdentity,
+ datasets::DefaultDS,
+ messages::{DelayRespMessage, FollowUpMessage, Header, Message, MessageBody, SyncMessage},
+ },
+ port::{
+ sequence_id::SequenceIdGenerator, Measurement, PortAction, PortActionIterator,
+ TimestampContext, TimestampContextInner,
+ },
+ time::{Duration, Time},
+ Clock, DelayMechanism, Filter, PortConfig,
+};
+
+#[derive(Debug)]
+pub(crate) struct SlaveState<F> {
+ remote_master: PortIdentity,
+
+ sync_state: SyncState,
+ delay_state: DelayState,
+
+ mean_delay: Option<Duration>,
+ last_raw_sync_offset: Option<Duration>,
+
+ delay_req_ids: SequenceIdGenerator,
+
+ filter: F,
+}
+
+impl<F> SlaveState<F> {
+ pub(crate) fn remote_master(&self) -> PortIdentity {
+ self.remote_master
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum SyncState {
+ Empty,
+ Measuring {
+ id: u16,
+ send_time: Option<Time>,
+ recv_time: Option<Time>,
+ },
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum DelayState {
+ Empty,
+ Measuring {
+ id: u16,
+ send_time: Option<Time>,
+ recv_time: Option<Time>,
+ },
+}
+
+impl<F: Filter> SlaveState<F> {
+ pub(crate) fn new(remote_master: PortIdentity, filter_config: F::Config) -> Self {
+ SlaveState {
+ remote_master,
+ sync_state: SyncState::Empty,
+ delay_state: DelayState::Empty,
+ mean_delay: None,
+ last_raw_sync_offset: None,
+ delay_req_ids: SequenceIdGenerator::new(),
+ filter: F::new(filter_config),
+ }
+ }
+
+ fn handle_time_measurement<'a, C: Clock>(&mut self, clock: &mut C) -> PortActionIterator<'a> {
+ if let Some(measurement) = self.extract_measurement() {
+ // If the received message allowed the (slave) state to calculate its offset
+ // from the master, update the local clock
+ let filter_updates = self.filter.measurement(measurement, clock);
+ if let Some(mean_delay) = filter_updates.mean_delay {
+ self.mean_delay = Some(mean_delay);
+ }
+ PortActionIterator::from_filter(filter_updates)
+ } else {
+ actions![]
+ }
+ }
+
+ pub(crate) fn handle_timestamp<'a, C: Clock>(
+ &mut self,
+ context: TimestampContext,
+ timestamp: Time,
+ clock: &mut C,
+ ) -> PortActionIterator<'a> {
+ match context.inner {
+ crate::port::TimestampContextInner::DelayReq { id } => {
+ // handle our send timestamp on a delay request message
+ self.handle_delay_timestamp(id, timestamp, clock)
+ }
+ _ => {
+ log::error!("Unexpected timestamp");
+ actions![]
+ }
+ }
+ }
+
+ fn handle_delay_timestamp<'a, C: Clock>(
+ &mut self,
+ timestamp_id: u16,
+ timestamp: Time,
+ clock: &mut C,
+ ) -> PortActionIterator<'a> {
+ match self.delay_state {
+ DelayState::Measuring {
+ id,
+ send_time: Some(_),
+ ..
+ } if id == timestamp_id => {
+ log::error!("Double send timestamp for delay request");
+ actions![]
+ }
+ DelayState::Measuring {
+ id,
+ ref mut send_time,
+ ..
+ } if id == timestamp_id => {
+ *send_time = Some(timestamp);
+ self.handle_time_measurement(clock)
+ }
+ _ => {
+ log::warn!("Late timestamp for delay request ignored");
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn handle_event_receive<'a, C: Clock>(
+ &mut self,
+ message: Message,
+ timestamp: Time,
+ clock: &mut C,
+ ) -> PortActionIterator<'a> {
+ // Ignore everything not from master
+ let header = &message.header;
+
+ if header.source_port_identity != self.remote_master {
+ return actions![];
+ }
+
+ match message.body {
+ MessageBody::Sync(sync) => self.handle_sync(header, sync, timestamp, clock),
+ _ => {
+ log::warn!("Unexpected message {:?}", message);
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn handle_general_receive<C: Clock>(
+ &mut self,
+ message: Message,
+ port_identity: PortIdentity,
+ clock: &mut C,
+ ) -> PortActionIterator {
+ let header = &message.header;
+
+ // Ignore everything not from master
+ if header.source_port_identity != self.remote_master {
+ return actions![];
+ }
+
+ match message.body {
+ MessageBody::FollowUp(message) => self.handle_follow_up(header, message, clock),
+ MessageBody::DelayResp(message) => {
+ self.handle_delay_resp(header, message, port_identity, clock)
+ }
+ _ => {
+ log::warn!("Unexpected message {:?}", message);
+ actions![]
+ }
+ }
+ }
+
+ pub(crate) fn handle_filter_update<'a, C: Clock>(
+ &mut self,
+ clock: &mut C,
+ ) -> PortActionIterator<'a> {
+ PortActionIterator::from_filter(self.filter.update(clock))
+ }
+
+ pub(crate) fn demobilize_filter<C: Clock>(self, clock: &mut C) {
+ self.filter.demobilize(clock);
+ }
+
+ fn handle_sync<'a, C: Clock>(
+ &mut self,
+ header: &Header,
+ message: SyncMessage,
+ recv_time: Time,
+ clock: &mut C,
+ ) -> PortActionIterator<'a> {
+ log::debug!("Received sync {:?}", header.sequence_id);
+
+ // substracting correction from recv time is equivalent to adding it to send
+ // time
+ let corrected_recv_time = recv_time - Duration::from(header.correction_field);
+
+ if header.two_step_flag {
+ match self.sync_state {
+ SyncState::Measuring {
+ id,
+ recv_time: Some(_),
+ ..
+ } if id == header.sequence_id => {
+ log::warn!("Duplicate sync message");
+ // Ignore the sync message
+ actions![]
+ }
+ SyncState::Measuring {
+ id,
+ ref mut recv_time,
+ ..
+ } if id == header.sequence_id => {
+ *recv_time = Some(corrected_recv_time);
+ self.handle_time_measurement(clock)
+ }
+ _ => {
+ self.sync_state = SyncState::Measuring {
+ id: header.sequence_id,
+ send_time: None,
+ recv_time: Some(corrected_recv_time),
+ };
+ actions![]
+ }
+ }
+ } else {
+ match self.sync_state {
+ SyncState::Measuring { id, .. } if id == header.sequence_id => {
+ log::warn!("Duplicate sync message");
+ // Ignore the sync message
+ actions![]
+ }
+ _ => {
+ self.sync_state = SyncState::Measuring {
+ id: header.sequence_id,
+ send_time: Some(Time::from(message.origin_timestamp)),
+ recv_time: Some(corrected_recv_time),
+ };
+ self.handle_time_measurement(clock)
+ }
+ }
+ }
+ }
+
+ fn handle_follow_up<C: Clock>(
+ &mut self,
+ header: &Header,
+ message: FollowUpMessage,
+ clock: &mut C,
+ ) -> PortActionIterator {
+ log::debug!("Received FollowUp {:?}", header.sequence_id);
+
+ let packet_send_time =
+ Time::from(message.precise_origin_timestamp) + Duration::from(header.correction_field);
+
+ match self.sync_state {
+ SyncState::Measuring {
+ id,
+ send_time: Some(_),
+ ..
+ } if id == header.sequence_id => {
+ log::warn!("Duplicate FollowUp message");
+ // Ignore the followup
+ actions![]
+ }
+ SyncState::Measuring {
+ id,
+ ref mut send_time,
+ ..
+ } if id == header.sequence_id => {
+ *send_time = Some(packet_send_time);
+ self.handle_time_measurement(clock)
+ }
+ _ => {
+ self.sync_state = SyncState::Measuring {
+ id: header.sequence_id,
+ send_time: Some(packet_send_time),
+ recv_time: None,
+ };
+ self.handle_time_measurement(clock)
+ }
+ }
+ }
+
+ fn handle_delay_resp<C: Clock>(
+ &mut self,
+ header: &Header,
+ message: DelayRespMessage,
+ port_identity: PortIdentity,
+ clock: &mut C,
+ ) -> PortActionIterator {
+ log::debug!("Received DelayResp");
+ if port_identity != message.requesting_port_identity {
+ return actions![];
+ }
+
+ match self.delay_state {
+ DelayState::Measuring {
+ id,
+ recv_time: Some(_),
+ ..
+ } if id == header.sequence_id => {
+ log::warn!("Duplicate DelayResp message");
+ // Ignore the Delay response
+ actions![]
+ }
+ DelayState::Measuring {
+ id,
+ ref mut recv_time,
+ ..
+ } if id == header.sequence_id => {
+ *recv_time = Some(
+ Time::from(message.receive_timestamp) - Duration::from(header.correction_field),
+ );
+ self.handle_time_measurement(clock)
+ }
+ _ => {
+ log::warn!("Unexpected DelayResp message");
+ // Ignore the Delay response
+ actions![]
+ }
+ }
+ }
+}
+
+impl<F> SlaveState<F> {
+ pub(crate) fn send_delay_request<'a>(
+ &mut self,
+ rng: &mut impl Rng,
+ port_config: &PortConfig<()>,
+ port_identity: PortIdentity,
+ default_ds: &DefaultDS,
+ buffer: &'a mut [u8],
+ ) -> PortActionIterator<'a> {
+ log::debug!("Starting new delay measurement");
+
+ let delay_id = self.delay_req_ids.generate();
+ let delay_req = Message::delay_req(default_ds, port_identity, delay_id);
+
+ let message_length = match delay_req.serialize(buffer) {
+ Ok(length) => length,
+ Err(error) => {
+ log::error!("Could not serialize delay request: {:?}", error);
+ return actions![];
+ }
+ };
+
+ self.delay_state = DelayState::Measuring {
+ id: delay_id,
+ send_time: None,
+ recv_time: None,
+ };
+
+ let random = rng.sample::<f64, _>(rand::distributions::Open01);
+ let log_min_delay_req_interval = match port_config.delay_mechanism {
+ // the interval corresponds to the PortDS logMinDelayReqInterval
+ DelayMechanism::E2E { interval } => interval,
+ };
+ let factor = random * 2.0f64;
+ let duration = log_min_delay_req_interval
+ .as_core_duration()
+ .mul_f64(factor);
+
+ actions![
+ PortAction::ResetDelayRequestTimer { duration },
+ PortAction::SendTimeCritical {
+ context: TimestampContext {
+ inner: TimestampContextInner::DelayReq { id: delay_id },
+ },
+ data: &buffer[..message_length],
+ }
+ ]
+ }
+
+ fn extract_measurement(&mut self) -> Option<Measurement> {
+ let mut result = Measurement::default();
+
+ if let SyncState::Measuring {
+ send_time: Some(send_time),
+ recv_time: Some(recv_time),
+ ..
+ } = self.sync_state
+ {
+ let raw_sync_offset = recv_time - send_time;
+ result.event_time = recv_time;
+ result.raw_sync_offset = Some(raw_sync_offset);
+
+ if let Some(mean_delay) = self.mean_delay {
+ result.offset = Some(raw_sync_offset - mean_delay);
+ }
+
+ self.last_raw_sync_offset = Some(raw_sync_offset);
+ self.sync_state = SyncState::Empty;
+ } else if let DelayState::Measuring {
+ send_time: Some(send_time),
+ recv_time: Some(recv_time),
+ ..
+ } = self.delay_state
+ {
+ let raw_delay_offset = send_time - recv_time;
+ result.event_time = send_time;
+ result.raw_delay_offset = Some(raw_delay_offset);
+
+ if let Some(raw_sync_offset) = self.last_raw_sync_offset {
+ result.delay = Some((raw_sync_offset - raw_delay_offset) / 2);
+ }
+
+ self.delay_state = DelayState::Empty;
+ } else {
+ // No measurement
+ return None;
+ }
+
+ log::info!("Measurement: {:?}", result);
+
+ Some(result)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ config::InstanceConfig,
+ datastructures::{
+ common::{ClockIdentity, TimeInterval, TlvSet},
+ messages::{Header, SdoId},
+ },
+ filters::FilterUpdate,
+ Interval, MAX_DATA_LEN,
+ };
+
+ struct TestFilter {
+ last_measurement: Option<Measurement>,
+ }
+
+ impl Filter for TestFilter {
+ type Config = ();
+
+ fn new(_config: Self::Config) -> Self {
+ Self {
+ last_measurement: None,
+ }
+ }
+
+ fn measurement<C: Clock>(&mut self, m: Measurement, _clock: &mut C) -> FilterUpdate {
+ self.last_measurement = Some(m);
+ if let Some(delay) = m.delay {
+ FilterUpdate {
+ next_update: None,
+ mean_delay: Some(delay),
+ }
+ } else {
+ Default::default()
+ }
+ }
+
+ fn demobilize<C: Clock>(self, _clock: &mut C) {
+ Default::default()
+ }
+
+ fn update<C: Clock>(&mut self, _clock: &mut C) -> FilterUpdate {
+ Default::default()
+ }
+ }
+
+ struct TestClock;
+
+ impl Clock for TestClock {
+ type Error = ();
+
+ fn set_frequency(&mut self, _freq: f64) -> Result<Time, Self::Error> {
+ Ok(Time::default())
+ }
+
+ fn now(&self) -> Time {
+ panic!("Shouldn't be called");
+ }
+
+ fn set_properties(
+ &mut self,
+ _time_properties_ds: &crate::TimePropertiesDS,
+ ) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn step_clock(&mut self, _offset: Duration) -> Result<Time, Self::Error> {
+ Ok(Time::default())
+ }
+ }
+
+ #[test]
+ fn test_sync_without_delay_msg() {
+ let mut state = SlaveState::<TestFilter>::new(Default::default(), ());
+ state.mean_delay = Some(Duration::from_micros(100));
+
+ let header = Header {
+ two_step_flag: false,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ };
+
+ let body = MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ });
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header,
+ body,
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(50),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(49),
+ offset: Some(Duration::from_micros(-51)),
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(49)),
+ raw_delay_offset: None,
+ })
+ );
+
+ let header = Header {
+ two_step_flag: true,
+ sequence_id: 15,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ };
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header,
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(1050),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let header = Header {
+ sequence_id: 15,
+ correction_field: TimeInterval(2000.into()),
+ ..Default::default()
+ };
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header,
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: Time::from_micros(1000).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(1049),
+ offset: Some(Duration::from_micros(-53)),
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(47)),
+ raw_delay_offset: None,
+ })
+ );
+ }
+
+ #[test]
+ fn test_sync_with_delay() {
+ let mut state = SlaveState::<TestFilter>::new(Default::default(), ());
+
+ let mut buffer = [0u8; MAX_DATA_LEN];
+ let default_ds = DefaultDS::new(InstanceConfig {
+ clock_identity: ClockIdentity::default(),
+ priority_1: 15,
+ priority_2: 128,
+ domain_number: 0,
+ slave_only: false,
+ sdo_id: SdoId::default(),
+ });
+
+ // mock rng and port config
+ let mut rng = rand::rngs::mock::StepRng::new(2, 1);
+ let port_identity = Default::default();
+ let port_config = PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: DelayMechanism::E2E {
+ interval: Interval::ONE_SECOND,
+ },
+ announce_interval: Interval::ONE_SECOND,
+ announce_receipt_timeout: Default::default(),
+ sync_interval: Interval::ONE_SECOND,
+ master_only: Default::default(),
+ delay_asymmetry: Default::default(),
+ };
+
+ let header = Header {
+ two_step_flag: false,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ };
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header,
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(50),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(49),
+ offset: None,
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(49)),
+ raw_delay_offset: None,
+ })
+ );
+
+ let mut action = state.send_delay_request(
+ &mut rng,
+ &port_config,
+ port_identity,
+ &default_ds,
+ &mut buffer,
+ );
+
+ let Some(PortAction::ResetDelayRequestTimer { .. }) = action.next() else {
+ panic!("Unexpected action");
+ };
+
+ let Some(PortAction::SendTimeCritical { context, data }) = action.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let req = Message::deserialize(data).unwrap();
+ let req_header = req.header;
+
+ let _req = match req.body {
+ MessageBody::DelayReq(msg) => msg,
+ _ => panic!("Incorrect message type"),
+ };
+
+ let mut action = state.handle_timestamp(context, Time::from_micros(100), &mut TestClock);
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let header = Header {
+ correction_field: TimeInterval(2000.into()),
+ sequence_id: req_header.sequence_id,
+ ..Default::default()
+ };
+
+ let body = MessageBody::DelayResp(DelayRespMessage {
+ receive_timestamp: Time::from_micros(253).into(),
+ requesting_port_identity: req_header.source_port_identity,
+ });
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header,
+ body,
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.mean_delay, Some(Duration::from_micros(100)));
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(100),
+ offset: None,
+ delay: Some(Duration::from_micros(100)),
+ raw_sync_offset: None,
+ raw_delay_offset: Some(Duration::from_micros(-151)),
+ })
+ );
+
+ state.mean_delay = None;
+
+ let header = Header {
+ two_step_flag: true,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ };
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header,
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(1050),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.send_delay_request(
+ &mut rng,
+ &port_config,
+ port_identity,
+ &default_ds,
+ &mut buffer,
+ );
+
+ let Some(PortAction::ResetDelayRequestTimer { .. }) = action.next() else {
+ panic!("Unexpected action");
+ };
+
+ let Some(PortAction::SendTimeCritical { context, data }) = action.next() else {
+ panic!("Unexpected action");
+ };
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let req = Message::deserialize(data).unwrap();
+ let req_header = req.header;
+
+ let _req = match req.body {
+ MessageBody::DelayReq(msg) => msg,
+ _ => panic!("Incorrect message type"),
+ };
+
+ let mut action = state.handle_timestamp(context, Time::from_micros(1100), &mut TestClock);
+ assert!(action.next().is_none());
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ correction_field: TimeInterval(2000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: Time::from_micros(1000).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(1049),
+ offset: None,
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(47)),
+ raw_delay_offset: None,
+ })
+ );
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ correction_field: TimeInterval(2000.into()),
+ sequence_id: req_header.sequence_id,
+ ..Default::default()
+ },
+ body: MessageBody::DelayResp(DelayRespMessage {
+ receive_timestamp: Time::from_micros(1255).into(),
+ requesting_port_identity: req_header.source_port_identity,
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.mean_delay, Some(Duration::from_micros(100)));
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(1100),
+ offset: None,
+ delay: Some(Duration::from_micros(100)),
+ raw_sync_offset: None,
+ raw_delay_offset: Some(Duration::from_micros(-153)),
+ })
+ );
+ }
+
+ #[test]
+ fn test_follow_up_before_sync() {
+ let mut state = SlaveState::<TestFilter>::new(Default::default(), ());
+ state.mean_delay = Some(Duration::from_micros(100));
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ sequence_id: 15,
+ correction_field: TimeInterval(2000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: Time::from_micros(10).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ two_step_flag: true,
+ sequence_id: 15,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(50),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(49),
+ offset: Some(Duration::from_micros(-63)),
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(37)),
+ raw_delay_offset: None,
+ })
+ );
+ }
+
+ #[test]
+ fn test_old_followup_during() {
+ let mut state = SlaveState::<TestFilter>::new(Default::default(), ());
+ state.mean_delay = Some(Duration::from_micros(100));
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ two_step_flag: true,
+ sequence_id: 15,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(50),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ sequence_id: 14,
+ correction_field: TimeInterval(2000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: Time::from_micros(10).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ sequence_id: 15,
+ correction_field: TimeInterval(2000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: Time::from_micros(10).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.filter.last_measurement.take(), None);
+ }
+
+ #[test]
+ fn test_reset_after_missing_followup() {
+ let mut state = SlaveState::<TestFilter>::new(Default::default(), ());
+ state.mean_delay = Some(Duration::from_micros(100));
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ two_step_flag: true,
+ sequence_id: 14,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(50),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ two_step_flag: true,
+ sequence_id: 15,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(1050),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ sequence_id: 15,
+ correction_field: TimeInterval(2000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::FollowUp(FollowUpMessage {
+ precise_origin_timestamp: Time::from_micros(1000).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(1049),
+ offset: Some(Duration::from_micros(-53)),
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(47)),
+ raw_delay_offset: None,
+ })
+ );
+ }
+
+ #[test]
+ fn test_ignore_unrelated_delayresp() {
+ let mut state = SlaveState::<TestFilter>::new(Default::default(), ());
+ let mut buffer = [0u8; MAX_DATA_LEN];
+
+ let default_ds = DefaultDS::new(InstanceConfig {
+ clock_identity: ClockIdentity::default(),
+ priority_1: 15,
+ priority_2: 128,
+ domain_number: 0,
+ slave_only: false,
+ sdo_id: SdoId::default(),
+ });
+
+ // mock rng and port config
+ let mut rng = rand::rngs::mock::StepRng::new(2, 1);
+ let port_identity = Default::default();
+ let port_config = PortConfig {
+ acceptable_master_list: (),
+ delay_mechanism: DelayMechanism::E2E {
+ interval: Interval::ONE_SECOND,
+ },
+ announce_interval: Interval::ONE_SECOND,
+ announce_receipt_timeout: Default::default(),
+ sync_interval: Interval::ONE_SECOND,
+ master_only: Default::default(),
+ delay_asymmetry: Default::default(),
+ };
+
+ let mut action = state.handle_event_receive(
+ Message {
+ header: Header {
+ two_step_flag: false,
+ correction_field: TimeInterval(1000.into()),
+ ..Default::default()
+ },
+ body: MessageBody::Sync(SyncMessage {
+ origin_timestamp: Time::from_micros(0).into(),
+ }),
+ suffix: TlvSet::default(),
+ },
+ Time::from_micros(50),
+ &mut TestClock,
+ );
+
+ // DelayReq is sent independently
+ assert!(action.next().is_none());
+ drop(action);
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(49),
+ offset: None,
+ delay: None,
+ raw_sync_offset: Some(Duration::from_micros(49)),
+ raw_delay_offset: None,
+ })
+ );
+
+ let mut action = state.send_delay_request(
+ &mut rng,
+ &port_config,
+ port_identity,
+ &default_ds,
+ &mut buffer,
+ );
+
+ let Some(PortAction::ResetDelayRequestTimer { .. }) = action.next() else {
+ panic!("Unexpected action");
+ };
+
+ let Some(PortAction::SendTimeCritical { context, data }) = action.next() else {
+ panic!("Unexpected action");
+ };
+
+ let mut action = state.handle_timestamp(context, Time::from_micros(100), &mut TestClock);
+
+ assert!(action.next().is_none());
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let req = Message::deserialize(data).unwrap();
+ let req_header = req.header;
+
+ let _req = match req.body {
+ MessageBody::DelayReq(msg) => msg,
+ _ => panic!("Incorrect message type"),
+ };
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ correction_field: TimeInterval(2000.into()),
+ sequence_id: req_header.sequence_id,
+ ..Default::default()
+ },
+ body: MessageBody::DelayResp(DelayRespMessage {
+ receive_timestamp: Time::from_micros(353).into(),
+ requesting_port_identity: PortIdentity {
+ port_number: 83,
+ ..Default::default()
+ },
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ correction_field: TimeInterval(2000.into()),
+ sequence_id: req_header.sequence_id.wrapping_sub(1),
+ ..Default::default()
+ },
+ body: MessageBody::DelayResp(DelayRespMessage {
+ receive_timestamp: Time::from_micros(353).into(),
+ requesting_port_identity: req_header.source_port_identity,
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.filter.last_measurement.take(), None);
+
+ let mut action = state.handle_general_receive(
+ Message {
+ header: Header {
+ correction_field: TimeInterval(2000.into()),
+ sequence_id: req_header.sequence_id,
+ ..Default::default()
+ },
+ body: MessageBody::DelayResp(DelayRespMessage {
+ receive_timestamp: Time::from_micros(253).into(),
+ requesting_port_identity: req_header.source_port_identity,
+ }),
+ suffix: TlvSet::default(),
+ },
+ PortIdentity::default(),
+ &mut TestClock,
+ );
+
+ assert!(action.next().is_none());
+ drop(action);
+
+ assert_eq!(state.mean_delay, Some(Duration::from_micros(100)));
+ assert_eq!(
+ state.filter.last_measurement.take(),
+ Some(Measurement {
+ event_time: Time::from_micros(100),
+ offset: None,
+ delay: Some(Duration::from_micros(100)),
+ raw_sync_offset: None,
+ raw_delay_offset: Some(Duration::from_micros(-151)),
+ })
+ );
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +
use core::{
+ marker::PhantomData,
+ sync::atomic::{AtomicI8, Ordering},
+};
+
+use atomic_refcell::AtomicRefCell;
+use rand::Rng;
+
+use crate::{
+ bmc::{acceptable_master::AcceptableMasterList, bmca::Bmca},
+ clock::Clock,
+ config::InstanceConfig,
+ datastructures::{
+ common::PortIdentity,
+ datasets::{CurrentDS, DefaultDS, ParentDS, TimePropertiesDS},
+ },
+ port::{InBmca, Port},
+ Filter, PortConfig,
+};
+
+/// A PTP node.
+///
+/// This object handles the complete running of the PTP protocol once created.
+/// It provides all the logic for both ordinary and boundary clock mode.
+///
+/// # Example
+/// Assuming we already have a network runtime and clock runtime, an ordinary
+/// clock can be run by first creating all the datasets, then creating the port,
+/// then finally setting up the instance and starting it:
+///
+/// ```ignore
+/// let default_ds = DefaultDS::new_ordinary_clock(
+/// clock_identity,
+/// 128,
+/// 128,
+/// 0,
+/// false,
+/// SdoId::new(0).unwrap(),
+/// );
+/// let time_properties_ds =
+/// TimePropertiesDS::new_arbitrary_time(false, false, TimeSource::InternalOscillator);
+/// let port_ds = PortDS::new(
+/// PortIdentity {
+/// clock_identity,
+/// port_number: 1,
+/// },
+/// 1,
+/// 1,
+/// 3,
+/// 0,
+/// DelayMechanism::E2E,
+/// 1,
+/// );
+/// let port = Port::new(port_ds, &mut network_runtime, interface_name).await;
+/// let mut instance = PtpInstance::new_ordinary_clock(
+/// default_ds,
+/// time_properties_ds,
+/// port,
+/// local_clock,
+/// BasicFilter::new(0.25),
+/// );
+///
+/// instance.run(&TimerImpl).await;
+/// ```
+pub struct PtpInstance<F> {
+ state: AtomicRefCell<PtpInstanceState>,
+ log_bmca_interval: AtomicI8,
+ _filter: PhantomData<F>,
+}
+
+#[derive(Debug)]
+pub(crate) struct PtpInstanceState {
+ pub(crate) default_ds: DefaultDS,
+ pub(crate) current_ds: CurrentDS,
+ pub(crate) parent_ds: ParentDS,
+ pub(crate) time_properties_ds: TimePropertiesDS,
+}
+
+impl PtpInstanceState {
+ fn bmca<A: AcceptableMasterList, C: Clock, F: Filter, R: Rng>(
+ &mut self,
+ ports: &mut [&mut Port<InBmca<'_>, A, R, C, F>],
+ bmca_interval: crate::Duration,
+ ) {
+ debug_assert_eq!(self.default_ds.number_ports as usize, ports.len());
+
+ for port in ports.iter_mut() {
+ port.calculate_best_local_announce_message()
+ }
+
+ let ebest = Bmca::<()>::find_best_announce_message(
+ ports
+ .iter()
+ .filter_map(|port| port.best_local_announce_message()),
+ );
+
+ for port in ports.iter_mut() {
+ let recommended_state = Bmca::<()>::calculate_recommended_state(
+ &self.default_ds,
+ ebest,
+ port.best_local_announce_message(), // erbest
+ port.state(),
+ );
+
+ log::debug!(
+ "Recommended state port {}: {recommended_state:?}",
+ port.number(),
+ );
+
+ if let Some(recommended_state) = recommended_state {
+ port.set_recommended_state(
+ recommended_state,
+ &mut self.time_properties_ds,
+ &mut self.current_ds,
+ &mut self.parent_ds,
+ &self.default_ds,
+ );
+ }
+ }
+
+ // And update announce message ages
+ for port in ports.iter_mut() {
+ port.step_announce_age(bmca_interval);
+ }
+ }
+}
+
+impl<F> PtpInstance<F> {
+ pub fn new(config: InstanceConfig, time_properties_ds: TimePropertiesDS) -> Self {
+ let default_ds = DefaultDS::new(config);
+ Self {
+ state: AtomicRefCell::new(PtpInstanceState {
+ default_ds,
+ current_ds: Default::default(),
+ parent_ds: ParentDS::new(default_ds),
+ time_properties_ds,
+ }),
+ log_bmca_interval: AtomicI8::new(i8::MAX),
+ _filter: PhantomData,
+ }
+ }
+}
+
+impl<F: Filter> PtpInstance<F> {
+ /// Add and initialize this port
+ ///
+ /// We start in the BMCA state because that is convenient
+ ///
+ /// When providing the port with a different clock than the instance clock,
+ /// the caller is responsible for propagating any property changes to this
+ /// clock, and for synchronizing this clock with the instance clock as
+ /// appropriate based on the ports state.
+ pub fn add_port<A, C, R: Rng>(
+ &self,
+ config: PortConfig<A>,
+ filter_config: F::Config,
+ clock: C,
+ rng: R,
+ ) -> Port<InBmca<'_>, A, R, C, F> {
+ self.log_bmca_interval
+ .fetch_min(config.announce_interval.as_log_2(), Ordering::Relaxed);
+ let mut state = self.state.borrow_mut();
+ let port_identity = PortIdentity {
+ clock_identity: state.default_ds.clock_identity,
+ port_number: state.default_ds.number_ports,
+ };
+ state.default_ds.number_ports += 1;
+ Port::new(
+ &self.state,
+ config,
+ filter_config,
+ clock,
+ port_identity,
+ rng,
+ )
+ }
+
+ pub fn bmca<A: AcceptableMasterList, C: Clock, R: Rng>(
+ &self,
+ ports: &mut [&mut Port<InBmca<'_>, A, R, C, F>],
+ ) {
+ self.state.borrow_mut().bmca(
+ ports,
+ crate::Duration::from_seconds(
+ #[cfg(feature = "std")]
+ 2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32),
+ #[cfg(not(feature = "std"))]
+ libm::pow(2f64, self.log_bmca_interval.load(Ordering::Relaxed) as f64),
+ ),
+ )
+ }
+
+ pub fn bmca_interval(&self) -> core::time::Duration {
+ core::time::Duration::from_secs_f64(
+ #[cfg(feature = "std")]
+ 2f64.powi(self.log_bmca_interval.load(Ordering::Relaxed) as i32),
+ #[cfg(not(feature = "std"))]
+ libm::pow(2f64, self.log_bmca_interval.load(Ordering::Relaxed) as f64),
+ )
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +
//! Implementation of the [Duration] type
+
+use core::{
+ fmt::Display,
+ ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
+};
+
+use az::Az;
+use fixed::{
+ traits::{LossyInto, ToFixed},
+ types::I96F32,
+};
+
+use super::Interval;
+use crate::datastructures::common::TimeInterval;
+
+/// A duration is a span of time that can also be negative.
+///
+/// For example, the difference between two instants is a duration.
+/// And an instant plus a duration is another instant.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
+pub struct Duration {
+ /// Time in nanos
+ inner: I96F32,
+}
+
+impl Duration {
+ pub const ZERO: Duration = Duration {
+ inner: I96F32::ZERO,
+ };
+
+ /// Create an instance with the given amount of seconds
+ pub fn from_seconds(secs: f64) -> Self {
+ let inner = secs.az::<I96F32>() * 1_000_000_000.to_fixed::<I96F32>();
+ Self { inner }
+ }
+
+ /// Create an instance with the given amount of seconds
+ pub fn from_secs(secs: i64) -> Self {
+ let inner = secs.to_fixed::<I96F32>() * 1_000_000_000.to_fixed::<I96F32>();
+ Self { inner }
+ }
+
+ /// Create an instance with the given amount of milliseconds
+ pub fn from_millis(millis: i64) -> Self {
+ let inner = millis.to_fixed::<I96F32>() * 1_000_000.to_fixed::<I96F32>();
+ Self { inner }
+ }
+ /// Create an instance with the given amount of microseconds
+ pub fn from_micros(micros: i64) -> Self {
+ let inner = micros.to_fixed::<I96F32>() * 1_000.to_fixed::<I96F32>();
+ Self { inner }
+ }
+ /// Create an instance with the given amount of nanoseconds
+ pub fn from_nanos(nanos: i64) -> Self {
+ let inner = nanos.to_fixed::<I96F32>();
+ Self { inner }
+ }
+
+ /// Create an instance with the given amount of nanoseconds, using a fixed
+ /// point number so the subnanoseconds can be specified as well
+ pub fn from_fixed_nanos<F: ToFixed>(nanos: F) -> Self {
+ Self {
+ inner: nanos.to_fixed(),
+ }
+ }
+
+ /// Get the total amount of nanoseconds
+ pub fn nanos(&self) -> I96F32 {
+ self.inner
+ }
+
+ pub fn nanos_rounded(&self) -> i128 {
+ self.nanos().lossy_into()
+ }
+
+ /// Get the total amount of nanoseconds, losing some precision
+ pub fn nanos_lossy(&self) -> f64 {
+ self.nanos().lossy_into()
+ }
+
+ /// Get the total amount of seconds
+ pub fn secs(&self) -> i64 {
+ (self.inner / 1_000_000_000.to_fixed::<I96F32>()).to_num()
+ }
+
+ /// Get the total amount of seconds
+ pub fn seconds(&self) -> f64 {
+ self.inner.az::<f64>() / 1e9
+ }
+
+ /// Converts a log interval (as defined by the PTP spec) to a duration
+ pub fn from_log_interval(log_interval: i8) -> Self {
+ let seconds = libm::pow(2.0f64, log_interval as f64);
+ let nanos = seconds * 1_000_000_000.0;
+ Self::from_fixed_nanos(nanos)
+ }
+
+ /// Converts a interval (as defined by the PTP spec) to a duration
+ pub fn from_interval(interval: Interval) -> Self {
+ let seconds = interval.seconds();
+ let nanos = seconds * 1_000_000_000.0;
+ Self::from_fixed_nanos(nanos)
+ }
+
+ /// Takes the absolute (non-negative) value of the duration
+ pub fn abs(self) -> Duration {
+ Duration::from_fixed_nanos(self.nanos().abs())
+ }
+}
+
+impl From<TimeInterval> for Duration {
+ fn from(interval: TimeInterval) -> Self {
+ Self::from_fixed_nanos(interval.0)
+ }
+}
+
+impl From<Duration> for core::time::Duration {
+ fn from(value: Duration) -> Self {
+ core::time::Duration::from_nanos(value.nanos().saturating_to_num())
+ }
+}
+
+impl Neg for Duration {
+ type Output = Duration;
+
+ fn neg(self) -> Self::Output {
+ Self::from_fixed_nanos(-self.nanos())
+ }
+}
+
+impl Add for Duration {
+ type Output = Duration;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ Duration {
+ inner: self.nanos() + rhs.nanos(),
+ }
+ }
+}
+
+impl AddAssign for Duration {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = *self + rhs;
+ }
+}
+
+impl Sub for Duration {
+ type Output = Duration;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ self + -rhs
+ }
+}
+
+impl SubAssign for Duration {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = *self - rhs;
+ }
+}
+
+impl<TF: ToFixed> Mul<TF> for Duration {
+ type Output = Duration;
+
+ fn mul(self, rhs: TF) -> Self::Output {
+ Duration::from_fixed_nanos(self.nanos() * rhs.to_fixed::<I96F32>())
+ }
+}
+
+impl<TF: ToFixed> MulAssign<TF> for Duration {
+ fn mul_assign(&mut self, rhs: TF) {
+ *self = *self * rhs
+ }
+}
+
+impl<TF: ToFixed> Div<TF> for Duration {
+ type Output = Duration;
+
+ fn div(self, rhs: TF) -> Self::Output {
+ Duration::from_fixed_nanos(self.nanos() / rhs.to_fixed::<I96F32>())
+ }
+}
+
+impl<TF: ToFixed> DivAssign<TF> for Duration {
+ fn div_assign(&mut self, rhs: TF) {
+ *self = *self / rhs
+ }
+}
+
+impl Rem for Duration {
+ type Output = Duration;
+
+ fn rem(self, rhs: Self) -> Self::Output {
+ Duration::from_fixed_nanos(self.nanos() % rhs.nanos())
+ }
+}
+
+impl RemAssign for Duration {
+ fn rem_assign(&mut self, rhs: Self) {
+ *self = *self % rhs
+ }
+}
+
+impl Display for Duration {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}", self.inner)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn values() {
+ assert_eq!(
+ Duration::from_secs(10).nanos(),
+ 10_000_000_000u64.to_fixed::<I96F32>()
+ );
+ assert_eq!(
+ Duration::from_secs(-10).nanos(),
+ -(10_000_000_000u64.to_fixed::<I96F32>())
+ );
+ assert_eq!(
+ Duration::from_millis(10).nanos(),
+ 10_000_000u64.to_fixed::<I96F32>()
+ );
+ assert_eq!(
+ Duration::from_micros(10).nanos(),
+ 10_000u64.to_fixed::<I96F32>()
+ );
+ assert_eq!(Duration::from_nanos(10).nanos(), 10u64.to_fixed::<I96F32>());
+ assert_eq!(
+ Duration::from_fixed_nanos(10.123f64).nanos(),
+ 10.123f64.to_fixed::<I96F32>()
+ );
+ assert_eq!(Duration::from_secs(10).secs(), 10);
+ assert_eq!(Duration::from_millis(10).secs(), 0);
+ assert_eq!(Duration::from_millis(1001).secs(), 1);
+ }
+
+ #[test]
+ fn log_interval() {
+ assert_eq!(Duration::from_log_interval(0), Duration::from_secs(1));
+ assert_eq!(Duration::from_log_interval(-1), Duration::from_millis(500));
+ assert_eq!(Duration::from_log_interval(1), Duration::from_secs(2));
+ }
+
+ #[test]
+ fn interval() {
+ assert_eq!(
+ Duration::from_fixed_nanos(2.25f64),
+ Duration::from(TimeInterval(2.25f64.to_fixed()))
+ );
+ assert_eq!(
+ TimeInterval(2.25f64.to_fixed()),
+ Duration::from_fixed_nanos(2.25f64).into()
+ );
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +
//! Implementation of the [Time] type
+
+use core::{
+ fmt::Display,
+ ops::{Add, AddAssign, Sub, SubAssign},
+};
+
+use fixed::{
+ traits::{LosslessTryInto, LossyInto, ToFixed},
+ types::{U112F16, U96F32},
+};
+
+use super::duration::Duration;
+use crate::datastructures::common::WireTimestamp;
+
+/// Time represents a specific moment in time.
+///
+/// The starting 0 point depends on the timescale being used by PTP, but
+/// for most uses will be the unix epoch.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
+pub struct Time {
+ /// Time in nanos since start of timescale
+ inner: U96F32,
+}
+
+impl Time {
+ /// Create an instance with the given amount of seconds from the origin
+ pub fn from_secs(secs: u64) -> Self {
+ let inner = secs.to_fixed::<U96F32>() * 1_000_000_000.to_fixed::<U96F32>();
+ Self { inner }
+ }
+ /// Create an instance with the given amount of milliseconds from the origin
+ pub fn from_millis(millis: u64) -> Self {
+ let inner = millis.to_fixed::<U96F32>() * 1_000_000.to_fixed::<U96F32>();
+ Self { inner }
+ }
+ /// Create an instance with the given amount of microseconds from the origin
+ pub fn from_micros(micros: u64) -> Self {
+ let inner = micros.to_fixed::<U96F32>() * 1_000.to_fixed::<U96F32>();
+ Self { inner }
+ }
+ /// Create an instance with the given amount of nanoseconds from the origin
+ pub fn from_nanos(nanos: u64) -> Self {
+ let inner = nanos.to_fixed::<U96F32>();
+ Self { inner }
+ }
+ /// Create an instance with the given amount of nanoseconds from the origin,
+ /// using a fixed point number so the subnanoseconds can be specified as
+ /// well
+ pub fn from_fixed_nanos<F: ToFixed>(nanos: F) -> Self {
+ Self {
+ inner: nanos.to_fixed(),
+ }
+ }
+
+ pub fn from_nanos_subnanos(nanos: u64, subnanos: u32) -> Self {
+ let bits = (nanos as u128) << 32 | (subnanos as u128);
+ let inner = U96F32::from_bits(bits);
+ Self { inner }
+ }
+
+ /// Get the total amount of nanoseconds since the origin
+ pub fn nanos(&self) -> U96F32 {
+ self.inner
+ }
+ /// Get all the nanoseconds that are under a second
+ pub fn subsec_nanos(&self) -> u32 {
+ (self.inner % 1_000_000_000.to_fixed::<U96F32>()).to_num()
+ }
+ /// Get the total amount of seconds since the origin
+ pub fn secs(&self) -> u64 {
+ (self.inner / 1_000_000_000.to_fixed::<U96F32>()).to_num()
+ }
+ // Get the subnanosecond amount
+ pub(crate) fn subnano(&self) -> crate::datastructures::common::TimeInterval {
+ let inter: U112F16 = self.inner.frac().lossy_into();
+ // unwrap is ok since always less than 1.
+ crate::datastructures::common::TimeInterval(inter.lossless_try_into().unwrap())
+ }
+}
+
+impl From<WireTimestamp> for Time {
+ fn from(ts: WireTimestamp) -> Self {
+ Self::from_fixed_nanos(ts.seconds as i128 * 1_000_000_000i128 + ts.nanos as i128)
+ }
+}
+
+impl Add<Duration> for Time {
+ type Output = Time;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ if rhs.nanos().is_negative() {
+ Time {
+ inner: self.nanos() - rhs.nanos().unsigned_abs(),
+ }
+ } else {
+ Time {
+ inner: self.nanos() + rhs.nanos().unsigned_abs(),
+ }
+ }
+ }
+}
+
+impl AddAssign<Duration> for Time {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = *self + rhs;
+ }
+}
+
+impl Sub<Duration> for Time {
+ type Output = Time;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ self + -rhs
+ }
+}
+
+impl SubAssign<Duration> for Time {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = *self - rhs;
+ }
+}
+
+impl Sub<Time> for Time {
+ type Output = Duration;
+
+ fn sub(self, rhs: Time) -> Self::Output {
+ Duration::from_fixed_nanos(self.inner) - Duration::from_fixed_nanos(rhs.inner)
+ }
+}
+
+impl Display for Time {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}", self.inner)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn values() {
+ assert_eq!(
+ Time::from_secs(10).nanos(),
+ 10_000_000_000u64.to_fixed::<U96F32>()
+ );
+ assert_eq!(
+ Time::from_millis(10).nanos(),
+ 10_000_000u64.to_fixed::<U96F32>()
+ );
+ assert_eq!(
+ Time::from_micros(10).nanos(),
+ 10_000u64.to_fixed::<U96F32>()
+ );
+ assert_eq!(Time::from_nanos(10).nanos(), 10u64.to_fixed::<U96F32>());
+ assert_eq!(
+ Time::from_fixed_nanos(10.123f64).nanos(),
+ 10.123f64.to_fixed::<U96F32>()
+ );
+ assert_eq!(Time::from_secs(10).secs(), 10);
+ assert_eq!(Time::from_millis(10).secs(), 0);
+ assert_eq!(Time::from_millis(1001).secs(), 1);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Interval(i8);
+
+impl core::fmt::Debug for Interval {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Interval")
+ .field("seconds", &self.as_f64())
+ .field("log_base_2", &self.0)
+ .finish()
+ }
+}
+
+impl Interval {
+ pub const ONE_SECOND: Self = Self(0);
+ pub const TWO_SECONDS: Self = Self(1);
+
+ pub const fn from_log_2(log_2: i8) -> Self {
+ Self(log_2)
+ }
+
+ pub fn seconds(self) -> f64 {
+ self.as_f64()
+ }
+
+ pub fn as_duration(self) -> super::Duration {
+ super::Duration::from_interval(self)
+ }
+
+ pub fn as_core_duration(self) -> core::time::Duration {
+ core::time::Duration::from_secs_f64(self.seconds())
+ }
+
+ #[cfg(not(feature = "std"))]
+ pub fn as_f64(self) -> f64 {
+ libm::pow(2.0f64, self.0 as f64)
+ }
+
+ #[cfg(feature = "std")]
+ pub fn as_f64(self) -> f64 {
+ 2.0f64.powi(self.0 as i32)
+ }
+
+ pub fn as_log_2(self) -> i8 {
+ self.0
+ }
+}
+
+impl From<i8> for Interval {
+ fn from(value: i8) -> Self {
+ Self::from_log_2(value)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn two() {
+ assert_eq!(Interval::TWO_SECONDS.as_f64(), 2.0f64)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +
//! Implementation of the abstract clock for the linux platform
+
+use std::path::Path;
+
+use clock_steering::{unix::UnixClock, TimeOffset};
+use statime::{Clock, Duration, Time, TimePropertiesDS};
+
+#[derive(Debug, Clone)]
+pub struct LinuxClock {
+ pub clock: clock_steering::unix::UnixClock,
+}
+
+impl LinuxClock {
+ pub const CLOCK_REALTIME: Self = Self {
+ clock: UnixClock::CLOCK_REALTIME,
+ };
+
+ pub fn open(path: impl AsRef<Path>) -> std::io::Result<Self> {
+ let clock = UnixClock::open(path)?;
+
+ Ok(Self { clock })
+ }
+}
+
+impl clock_steering::Clock for LinuxClock {
+ type Error = clock_steering::unix::Error;
+
+ fn now(&self) -> Result<clock_steering::Timestamp, Self::Error> {
+ self.clock.now()
+ }
+
+ fn resolution(&self) -> Result<clock_steering::Timestamp, Self::Error> {
+ self.clock.resolution()
+ }
+
+ fn set_frequency(&self, frequency: f64) -> Result<clock_steering::Timestamp, Self::Error> {
+ self.clock.set_frequency(frequency)
+ }
+
+ fn step_clock(&self, offset: TimeOffset) -> Result<clock_steering::Timestamp, Self::Error> {
+ self.clock.step_clock(offset)
+ }
+
+ fn set_leap_seconds(
+ &self,
+ leap_status: clock_steering::LeapIndicator,
+ ) -> Result<(), Self::Error> {
+ self.clock.set_leap_seconds(leap_status)
+ }
+
+ fn error_estimate_update(
+ &self,
+ estimated_error: std::time::Duration,
+ maximum_error: std::time::Duration,
+ ) -> Result<(), Self::Error> {
+ self.clock
+ .error_estimate_update(estimated_error, maximum_error)
+ }
+}
+
+fn time_from_timestamp(timestamp: clock_steering::Timestamp, fallback: Time) -> Time {
+ let Ok(seconds): Result<u64, _> = timestamp.seconds.try_into() else {
+ return fallback;
+ };
+
+ let nanos = seconds * 1_000_000_000 + timestamp.nanos as u64;
+ Time::from_nanos_subnanos(nanos, 0)
+}
+
+impl Clock for LinuxClock {
+ type Error = clock_steering::unix::Error;
+
+ fn now(&self) -> Time {
+ use clock_steering::Clock;
+
+ let timestamp = self.clock.now().unwrap();
+ time_from_timestamp(timestamp, Time::from_fixed_nanos(0))
+ }
+
+ fn set_frequency(&mut self, freq: f64) -> Result<Time, Self::Error> {
+ use clock_steering::Clock;
+ log::trace!("Setting clock frequency to {:e}ppm", freq);
+ let timestamp = self.clock.set_frequency(freq)?;
+ Ok(time_from_timestamp(timestamp, statime::Clock::now(self)))
+ }
+
+ fn step_clock(&mut self, time_offset: Duration) -> Result<Time, Self::Error> {
+ use clock_steering::Clock;
+
+ // Since we want nanos to be in [0,1_000_000_000), we need
+ // euclidean division and remainder.
+ let offset_nanos: i128 = time_offset.nanos_rounded();
+ let offset = TimeOffset {
+ seconds: offset_nanos
+ .div_euclid(1_000_000_000)
+ .try_into()
+ .expect("Unexpected jump larger than 2^64 seconds"),
+ nanos: offset_nanos.rem_euclid(1_000_000_000) as _, // Result will always fit in u32
+ };
+
+ log::trace!(
+ "Stepping clock {:e}ns",
+ (offset.seconds as f64) * 1e9 + (offset.nanos as f64)
+ );
+
+ let timestamp = self.clock.step_clock(offset)?;
+ Ok(time_from_timestamp(timestamp, statime::Clock::now(self)))
+ }
+
+ fn set_properties(&mut self, _time_properties: &TimePropertiesDS) -> Result<(), Self::Error> {
+ // For now just ignore these
+
+ Ok(())
+ }
+}
+
+pub fn libc_timespec_into_instant(spec: libc::timespec) -> Time {
+ Time::from_fixed_nanos(spec.tv_sec as i128 * 1_000_000_000i128 + spec.tv_nsec as i128)
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +
use std::{fs::read_to_string, os::unix::fs::PermissionsExt, path::Path};
+
+use log::warn;
+use serde::{Deserialize, Deserializer};
+use statime::{ClockIdentity, DelayMechanism, Duration, Interval};
+use timestamped_socket::interface::InterfaceName;
+
+#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "kebab-case", deny_unknown_fields)]
+pub struct Config {
+ pub loglevel: String,
+ pub sdo_id: u16,
+ pub domain: u8,
+ pub priority1: u8,
+ pub priority2: u8,
+ #[serde(rename = "port")]
+ pub ports: Vec<PortConfig>,
+}
+
+#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct PortConfig {
+ pub interface: InterfaceName,
+ #[serde(default, deserialize_with = "deserialize_acceptable_master_list")]
+ pub acceptable_master_list: Option<Vec<ClockIdentity>>,
+ #[serde(default)]
+ pub hardware_clock: Option<String>,
+ #[serde(default)]
+ pub network_mode: NetworkMode,
+ pub announce_interval: i8,
+ pub sync_interval: i8,
+ pub announce_receipt_timeout: u8,
+ #[serde(default)]
+ pub master_only: bool,
+ pub delay_asymetry: i64,
+ pub delay_mechanism: i8,
+}
+
+fn deserialize_acceptable_master_list<'de, D>(
+ deserializer: D,
+) -> Result<Option<Vec<ClockIdentity>>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ use hex::FromHex;
+ use serde::de::Error;
+
+ let raw: Vec<&str> = Deserialize::deserialize(deserializer)?;
+ let mut result = Vec::with_capacity(raw.len());
+
+ for identity in raw {
+ result.push(ClockIdentity(<[u8; 8]>::from_hex(identity).map_err(
+ |e| D::Error::custom(format!("Invalid clock identifier: {}", e)),
+ )?));
+ }
+
+ Ok(Some(result))
+}
+
+impl From<PortConfig> for statime::PortConfig<Option<Vec<ClockIdentity>>> {
+ fn from(pc: PortConfig) -> Self {
+ Self {
+ acceptable_master_list: pc.acceptable_master_list,
+ announce_interval: Interval::from_log_2(pc.announce_interval),
+ sync_interval: Interval::from_log_2(pc.sync_interval),
+ announce_receipt_timeout: pc.announce_receipt_timeout,
+ master_only: pc.master_only,
+ delay_asymmetry: Duration::from_nanos(pc.delay_asymetry),
+ delay_mechanism: DelayMechanism::E2E {
+ interval: Interval::from_log_2(pc.delay_mechanism),
+ },
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
+#[serde(rename_all = "lowercase")]
+pub enum NetworkMode {
+ #[default]
+ Ipv4,
+ Ipv6,
+}
+
+impl Config {
+ /// Parse config from file
+ pub fn from_file(file: &Path) -> Result<Config, ConfigError> {
+ let meta = std::fs::metadata(file).unwrap();
+ let perm = meta.permissions();
+
+ if perm.mode() as libc::mode_t & libc::S_IWOTH != 0 {
+ warn!("Unrestricted config file permissions: Others can write.");
+ }
+
+ let contents = read_to_string(file).map_err(ConfigError::Io)?;
+ let config: Config = toml::de::from_str(&contents).map_err(ConfigError::Toml)?;
+ config.warn_when_unreasonable();
+ Ok(config)
+ }
+
+ /// Warns about unreasonable config values
+ pub fn warn_when_unreasonable(&self) {
+ if self.ports.is_empty() {
+ warn!("No ports configured.");
+ }
+
+ if self.ports.len() > 16 {
+ warn!("Too many ports are configured.");
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum ConfigError {
+ Io(std::io::Error),
+ Toml(toml::de::Error),
+}
+
+impl std::fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ConfigError::Io(e) => writeln!(f, "io error while reading config: {e}"),
+ ConfigError::Toml(e) => writeln!(f, "config toml parsing error: {e}"),
+ }
+ }
+}
+
+impl std::error::Error for ConfigError {}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +
#![forbid(unsafe_code)]
+
+//! Event and General sockets for linux systems
+
+use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
+
+use statime::Time;
+use timestamped_socket::{
+ interface::InterfaceName,
+ socket::{open_interface_udp4, open_interface_udp6, InterfaceTimestampMode, Open, Socket},
+};
+
+const IPV6_PRIMARY_MULTICAST: Ipv6Addr = Ipv6Addr::new(0xff, 0x0e, 0, 0, 0, 0, 0x01, 0x81);
+const IPV6_PDELAY_MULTICAST: Ipv6Addr = Ipv6Addr::new(0xff, 0x02, 0, 0, 0, 0, 0, 0x6b);
+
+const IPV4_PRIMARY_MULTICAST: Ipv4Addr = Ipv4Addr::new(224, 0, 1, 129);
+const IPV4_PDELAY_MULTICAST: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 107);
+
+const EVENT_PORT: u16 = 319;
+const GENERAL_PORT: u16 = 320;
+
+pub trait PtpTargetAddress {
+ const PRIMARY_EVENT: Self;
+ const PRIMARY_GENERAL: Self;
+ const PDELAY_EVENT: Self;
+ const PDELAY_GENERAL: Self;
+}
+
+impl PtpTargetAddress for SocketAddrV4 {
+ const PRIMARY_EVENT: Self = SocketAddrV4::new(IPV4_PRIMARY_MULTICAST, EVENT_PORT);
+ const PRIMARY_GENERAL: Self = SocketAddrV4::new(IPV4_PRIMARY_MULTICAST, GENERAL_PORT);
+ const PDELAY_EVENT: Self = SocketAddrV4::new(IPV4_PDELAY_MULTICAST, EVENT_PORT);
+ const PDELAY_GENERAL: Self = SocketAddrV4::new(IPV4_PDELAY_MULTICAST, GENERAL_PORT);
+}
+
+impl PtpTargetAddress for SocketAddrV6 {
+ const PRIMARY_EVENT: Self = SocketAddrV6::new(IPV6_PRIMARY_MULTICAST, EVENT_PORT, 0, 0);
+ const PRIMARY_GENERAL: Self = SocketAddrV6::new(IPV6_PRIMARY_MULTICAST, GENERAL_PORT, 0, 0);
+ const PDELAY_EVENT: Self = SocketAddrV6::new(IPV6_PDELAY_MULTICAST, EVENT_PORT, 0, 0);
+ const PDELAY_GENERAL: Self = SocketAddrV6::new(IPV6_PDELAY_MULTICAST, GENERAL_PORT, 0, 0);
+}
+
+pub fn open_ipv4_event_socket(
+ interface: InterfaceName,
+ timestamping: InterfaceTimestampMode,
+) -> std::io::Result<Socket<SocketAddrV4, Open>> {
+ let socket = open_interface_udp4(interface, EVENT_PORT, timestamping)?;
+ socket.join_multicast(SocketAddrV4::new(IPV4_PRIMARY_MULTICAST, 0), interface)?;
+ socket.join_multicast(SocketAddrV4::new(IPV4_PDELAY_MULTICAST, 0), interface)?;
+ Ok(socket)
+}
+
+pub fn open_ipv4_general_socket(
+ interface: InterfaceName,
+) -> std::io::Result<Socket<SocketAddrV4, Open>> {
+ let socket = open_interface_udp4(interface, GENERAL_PORT, InterfaceTimestampMode::None)?;
+ socket.join_multicast(SocketAddrV4::new(IPV4_PRIMARY_MULTICAST, 0), interface)?;
+ socket.join_multicast(SocketAddrV4::new(IPV4_PDELAY_MULTICAST, 0), interface)?;
+ Ok(socket)
+}
+
+pub fn open_ipv6_event_socket(
+ interface: InterfaceName,
+ timestamping: InterfaceTimestampMode,
+) -> std::io::Result<Socket<SocketAddrV6, Open>> {
+ let socket = open_interface_udp6(interface, EVENT_PORT, timestamping)?;
+ socket.join_multicast(
+ SocketAddrV6::new(IPV6_PRIMARY_MULTICAST, 0, 0, 0),
+ interface,
+ )?;
+ socket.join_multicast(SocketAddrV6::new(IPV6_PDELAY_MULTICAST, 0, 0, 0), interface)?;
+ Ok(socket)
+}
+
+pub fn open_ipv6_general_socket(
+ interface: InterfaceName,
+) -> std::io::Result<Socket<SocketAddrV6, Open>> {
+ let socket = open_interface_udp6(interface, GENERAL_PORT, InterfaceTimestampMode::None)?;
+ // Port, flowinfo and scope doesn't matter for join multicast
+ socket.join_multicast(
+ SocketAddrV6::new(IPV6_PRIMARY_MULTICAST, 0, 0, 0),
+ interface,
+ )?;
+ socket.join_multicast(SocketAddrV6::new(IPV6_PDELAY_MULTICAST, 0, 0, 0), interface)?;
+ Ok(socket)
+}
+
+pub fn timestamp_to_time(ts: timestamped_socket::socket::Timestamp) -> Time {
+ Time::from_fixed_nanos(ts.seconds as i128 * 1_000_000_000i128 + ts.nanos as i128)
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`Redirecting to ../../statime/trait.Clock.html...
+ + + \ No newline at end of file diff --git a/docs/statime/config/instance/struct.InstanceConfig.html b/docs/statime/config/instance/struct.InstanceConfig.html new file mode 100644 index 000000000..a25a4e693 --- /dev/null +++ b/docs/statime/config/instance/struct.InstanceConfig.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/struct.InstanceConfig.html...
+ + + \ No newline at end of file diff --git a/docs/statime/config/port/enum.DelayMechanism.html b/docs/statime/config/port/enum.DelayMechanism.html new file mode 100644 index 000000000..a592f9037 --- /dev/null +++ b/docs/statime/config/port/enum.DelayMechanism.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/enum.DelayMechanism.html...
+ + + \ No newline at end of file diff --git a/docs/statime/config/port/struct.PortConfig.html b/docs/statime/config/port/struct.PortConfig.html new file mode 100644 index 000000000..f862d1ad4 --- /dev/null +++ b/docs/statime/config/port/struct.PortConfig.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/struct.PortConfig.html...
+ + + \ No newline at end of file diff --git a/docs/statime/constant.MAX_DATA_LEN.html b/docs/statime/constant.MAX_DATA_LEN.html new file mode 100644 index 000000000..50794c066 --- /dev/null +++ b/docs/statime/constant.MAX_DATA_LEN.html @@ -0,0 +1 @@ +pub const MAX_DATA_LEN: usize = 255;
Redirecting to ../../../../statime/enum.ClockAccuracy.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/common/clock_identity/struct.ClockIdentity.html b/docs/statime/datastructures/common/clock_identity/struct.ClockIdentity.html new file mode 100644 index 000000000..489a6e2ff --- /dev/null +++ b/docs/statime/datastructures/common/clock_identity/struct.ClockIdentity.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/struct.ClockIdentity.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/common/clock_quality/struct.ClockQuality.html b/docs/statime/datastructures/common/clock_quality/struct.ClockQuality.html new file mode 100644 index 000000000..0570e83f4 --- /dev/null +++ b/docs/statime/datastructures/common/clock_quality/struct.ClockQuality.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/struct.ClockQuality.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/common/leap_indicator/enum.LeapIndicator.html b/docs/statime/datastructures/common/leap_indicator/enum.LeapIndicator.html new file mode 100644 index 000000000..c3b20443e --- /dev/null +++ b/docs/statime/datastructures/common/leap_indicator/enum.LeapIndicator.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/enum.LeapIndicator.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/common/time_source/enum.TimeSource.html b/docs/statime/datastructures/common/time_source/enum.TimeSource.html new file mode 100644 index 000000000..9ea07bc19 --- /dev/null +++ b/docs/statime/datastructures/common/time_source/enum.TimeSource.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/enum.TimeSource.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/datasets/time_properties/struct.TimePropertiesDS.html b/docs/statime/datastructures/datasets/time_properties/struct.TimePropertiesDS.html new file mode 100644 index 000000000..1bedc2858 --- /dev/null +++ b/docs/statime/datastructures/datasets/time_properties/struct.TimePropertiesDS.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/struct.TimePropertiesDS.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/messages/constant.MAX_DATA_LEN.html b/docs/statime/datastructures/messages/constant.MAX_DATA_LEN.html new file mode 100644 index 000000000..6d4f657f9 --- /dev/null +++ b/docs/statime/datastructures/messages/constant.MAX_DATA_LEN.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/constant.MAX_DATA_LEN.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/messages/fuzz/struct.FuzzMessage.html b/docs/statime/datastructures/messages/fuzz/struct.FuzzMessage.html new file mode 100644 index 000000000..87947b3df --- /dev/null +++ b/docs/statime/datastructures/messages/fuzz/struct.FuzzMessage.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/struct.FuzzMessage.html...
+ + + \ No newline at end of file diff --git a/docs/statime/datastructures/messages/header/struct.SdoId.html b/docs/statime/datastructures/messages/header/struct.SdoId.html new file mode 100644 index 000000000..a90d5a494 --- /dev/null +++ b/docs/statime/datastructures/messages/header/struct.SdoId.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../../statime/struct.SdoId.html...
+ + + \ No newline at end of file diff --git a/docs/statime/enum.ClockAccuracy.html b/docs/statime/enum.ClockAccuracy.html new file mode 100644 index 000000000..8cffcb7cc --- /dev/null +++ b/docs/statime/enum.ClockAccuracy.html @@ -0,0 +1,92 @@ +pub enum ClockAccuracy {
+Show 30 variants
Reserved,
+ PS1,
+ PS2_5,
+ PS10,
+ PS25,
+ PS100,
+ PS250,
+ NS1,
+ NS2_5,
+ NS10,
+ NS25,
+ NS100,
+ NS250,
+ US1,
+ US2_5,
+ US10,
+ US25,
+ US100,
+ US250,
+ MS1,
+ MS2_5,
+ MS10,
+ MS25,
+ MS100,
+ MS250,
+ S1,
+ S10,
+ SGT10,
+ ProfileSpecific(u8),
+ Unknown,
+}
How accurate the underlying clock device is expected to be when not +synchronized.
+Reserved
+Accurate within 1 ps
+Accurate within 2.5 ps
+Accurate within 10 ps
+Accurate within 25 ps
+Accurate within 100 ps
+Accurate within 250 ps
+Accurate within 1 ns
+Accurate within 2.5 ns
+Accurate within 10 ns
+Accurate within 25 ns
+Accurate within 100 ns
+Accurate within 250 ns
+Accurate within 1 us
+Accurate within 2.5 us
+Accurate within 10 us
+Accurate within 25 us
+Accurate within 100 us
+Accurate within 250 us
+Accurate within 1 ms
+Accurate within 2.5 ms
+Accurate within 10 ms
+Accurate within 25 ms
+Accurate within 100 ms
+Accurate within 250 ms
+Accurate within 1 s
+Accurate within 10 s
+Accurate within >10 s
+Specific to a profile
+Accuracy is unknown
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub enum DelayMechanism {
+ E2E {
+ interval: Interval,
+ },
+}
Which delay mechanism a port is using.
+Currently, statime only supports the end to end (E2E) delay mechanism.
+End to end delay mechanism. Delay measurement is done directly to the +chosen master, across potential transparent nodes in between.
+the interval corresponds to the PortDS logMinDelayReqInterval
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub enum LeapIndicator {
+ NoLeap,
+ Leap61,
+ Leap59,
+}
the last minute of the current UTC day contains 61 seconds.
+the last minute of the current UTC day contains 59 seconds.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub enum PortAction<'a> {
+ SendTimeCritical {
+ context: TimestampContext,
+ data: &'a [u8],
+ },
+ SendGeneral {
+ data: &'a [u8],
+ },
+ ResetAnnounceTimer {
+ duration: Duration,
+ },
+ ResetSyncTimer {
+ duration: Duration,
+ },
+ ResetDelayRequestTimer {
+ duration: Duration,
+ },
+ ResetAnnounceReceiptTimer {
+ duration: Duration,
+ },
+ ResetFilterUpdateTimer {
+ duration: Duration,
+ },
+}
pub enum TimeSource {
+ AtomicClock,
+ Gnss,
+ TerrestrialRadio,
+ SerialTimeCode,
+ Ptp,
+ Ntp,
+ HandSet,
+ Other,
+ InternalOscillator,
+ ProfileSpecific(u8),
+ Reserved,
+ Unknown(u8),
+}
What the time values for a system are derived from
+This enum encodes the root source of a system’s time values. For most use
+cases, the default InternalOscillator
will suffice.
Time source is unknown. This is not an official variant from the spec, +but we just need it in practise
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.Redirecting to ../../../statime/struct.BasicFilter.html...
+ + + \ No newline at end of file diff --git a/docs/statime/filters/trait.Filter.html b/docs/statime/filters/trait.Filter.html new file mode 100644 index 000000000..95da0038d --- /dev/null +++ b/docs/statime/filters/trait.Filter.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/trait.Filter.html...
+ + + \ No newline at end of file diff --git a/docs/statime/index.html b/docs/statime/index.html new file mode 100644 index 000000000..4445125cc --- /dev/null +++ b/docs/statime/index.html @@ -0,0 +1,33 @@ +Statime is a library providing an implementation of PTP version 2.1 +(IEEE1588-2019). It provides all the building blocks to setup PTP ordinary +and boundary clocks.
+Note: We are currently planning a major overhaul of the library. This will +also result in significant changes to the public API.
+statime
is designed to be able to work with many different underlying
+platforms, including embedded targets. This does mean that it cannot use the
+standard library and platform specific libraries to interact with the system
+clock and to access the network. That needs to be provided by the user of
+the library.
The statime
crate defines a Clock
interface that provide access to the
+system clock. The [NetworkRuntime
] and [NetworkPort
]
+abstractions provide the needed glue to access the network.
On modern linux kernels, the statime-linux
crate provides ready to use
+implementations of these interfaces. For other platforms the user will need
+to implement these themselves.
All ptp clocks in a network need a unique clock identity. One way to achieve +this is to use (one of) the device’s mac address to generate this +identifier. As this requires platform specific code to get the mac address, +this library does not implement this. Rather, direct access is given to the +clock identity type, and the user can create one from a mac address by +storing it in the first six bytes of the clock identifier, setting the +remaining bytes to 0. For more details on the exact specification of the +generation procedure, see IEEE1588-2019 section 7.5.2.2.2
+Redirecting to ../../statime/enum.PortAction.html...
+ + + \ No newline at end of file diff --git a/docs/statime/port/measurement/struct.Measurement.html b/docs/statime/port/measurement/struct.Measurement.html new file mode 100644 index 000000000..bc6e96b68 --- /dev/null +++ b/docs/statime/port/measurement/struct.Measurement.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/struct.Measurement.html...
+ + + \ No newline at end of file diff --git a/docs/statime/port/struct.InBmca.html b/docs/statime/port/struct.InBmca.html new file mode 100644 index 000000000..8561a3343 --- /dev/null +++ b/docs/statime/port/struct.InBmca.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/struct.InBmca.html...
+ + + \ No newline at end of file diff --git a/docs/statime/port/struct.Port.html b/docs/statime/port/struct.Port.html new file mode 100644 index 000000000..b6d704e0e --- /dev/null +++ b/docs/statime/port/struct.Port.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/struct.Port.html...
+ + + \ No newline at end of file diff --git a/docs/statime/port/struct.PortActionIterator.html b/docs/statime/port/struct.PortActionIterator.html new file mode 100644 index 000000000..21a462cf4 --- /dev/null +++ b/docs/statime/port/struct.PortActionIterator.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/struct.PortActionIterator.html...
+ + + \ No newline at end of file diff --git a/docs/statime/port/struct.Running.html b/docs/statime/port/struct.Running.html new file mode 100644 index 000000000..c6858757d --- /dev/null +++ b/docs/statime/port/struct.Running.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/struct.Running.html...
+ + + \ No newline at end of file diff --git a/docs/statime/port/struct.TimestampContext.html b/docs/statime/port/struct.TimestampContext.html new file mode 100644 index 000000000..4191b21fe --- /dev/null +++ b/docs/statime/port/struct.TimestampContext.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/struct.TimestampContext.html...
+ + + \ No newline at end of file diff --git a/docs/statime/ptp_instance/struct.PtpInstance.html b/docs/statime/ptp_instance/struct.PtpInstance.html new file mode 100644 index 000000000..eaa0aedad --- /dev/null +++ b/docs/statime/ptp_instance/struct.PtpInstance.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../statime/struct.PtpInstance.html...
+ + + \ No newline at end of file diff --git a/docs/statime/sidebar-items.js b/docs/statime/sidebar-items.js new file mode 100644 index 000000000..f3b103f8b --- /dev/null +++ b/docs/statime/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["MAX_DATA_LEN"],"enum":["ClockAccuracy","DelayMechanism","LeapIndicator","PortAction","TimeSource"],"struct":["BasicFilter","ClockIdentity","ClockQuality","Duration","FuzzMessage","InBmca","InstanceConfig","Interval","Measurement","Port","PortActionIterator","PortConfig","PtpInstance","Running","SdoId","Time","TimePropertiesDS","TimestampContext"],"trait":["Clock","Filter"]}; \ No newline at end of file diff --git a/docs/statime/struct.BasicFilter.html b/docs/statime/struct.BasicFilter.html new file mode 100644 index 000000000..a93d1d2de --- /dev/null +++ b/docs/statime/struct.BasicFilter.html @@ -0,0 +1,35 @@ +pub struct BasicFilter { /* private fields */ }
A simple averaging filter
+This filter uses simple averaging to determine what the clock control +outputs should be.
+pub struct ClockIdentity(pub [u8; 8]);
The identity of a PTP node.
+Must have a unique value for each node in a ptp network. For notes on +generating these, see IEEE1588-2019 section 7.5.2.2
+0: [u8; 8]
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read morepub struct ClockQuality {
+ pub clock_class: u8,
+ pub clock_accuracy: ClockAccuracy,
+ pub offset_scaled_log_variance: u16,
+}
A description of the accuracy and type of a clock.
+clock_class: u8
The PTP clock class.
+Per the standard, 248 is the default, and a good option for most use +cases. For grandmaster clocks, this should be below 128 to ensure the +clock never takes time from another source. A value of 6 is a good +option for a node with an external time source.
+For other potential values, see IEEE1588-2019 section 7.6.2.5
+clock_accuracy: ClockAccuracy
The accuracy of the clock
+offset_scaled_log_variance: u16
2-log of the variance (in seconds^2) of the clock when not synchronized. +See IEEE1588-2019 section 7.6.3.5 for more details.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct Duration { /* private fields */ }
A duration is a span of time that can also be negative.
+For example, the difference between two instants is a duration. +And an instant plus a duration is another instant.
+Create an instance with the given amount of seconds
+Create an instance with the given amount of milliseconds
+Create an instance with the given amount of microseconds
+Create an instance with the given amount of nanoseconds
+Create an instance with the given amount of nanoseconds, using a fixed +point number so the subnanoseconds can be specified as well
+Get the total amount of nanoseconds, losing some precision
+Converts a log interval (as defined by the PTP spec) to a duration
+Converts a interval (as defined by the PTP spec) to a duration
++=
operation. Read more+=
operation. Read more/=
operation. Read more*=
operation. Read moreself
and other
) and is used by the <=
+operator. Read more%=
operation. Read more-=
operation. Read more-=
operation. Read morepub struct FuzzMessage<'a> { /* private fields */ }
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct InBmca<'a> { /* private fields */ }
pub struct InstanceConfig {
+ pub clock_identity: ClockIdentity,
+ pub priority_1: u8,
+ pub priority_2: u8,
+ pub domain_number: u8,
+ pub slave_only: bool,
+ pub sdo_id: SdoId,
+}
clock_identity: ClockIdentity
§priority_1: u8
§priority_2: u8
§domain_number: u8
§slave_only: bool
§sdo_id: SdoId
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct Interval(/* private fields */);
self
and other
) and is used by the <=
+operator. Read morepub struct Measurement {
+ pub event_time: Time,
+ pub offset: Option<Duration>,
+ pub delay: Option<Duration>,
+ pub raw_sync_offset: Option<Duration>,
+ pub raw_delay_offset: Option<Duration>,
+}
A single measurement as produced by a PTP port. +Depending on what trigerred the measurements, not +all fields will be populated
+event_time: Time
Time this measurement was made.
+offset: Option<Duration>
Offset to the remote PTP node.
+delay: Option<Duration>
Delay to the remote PTP node.
+raw_sync_offset: Option<Duration>
Raw offset calculated from a sync message
+raw_delay_offset: Option<Duration>
Raw offset calculated from a delay message
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct Port<L, A, R, C, F: Filter> { /* private fields */ }
A single port of the PTP instance
+One of these needs to be created per port of the PTP instance.
+pub struct PortActionIterator<'a> { /* private fields */ }
Guarantees to end user: Any set of actions will only ever contain a single +time critical send
+iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)pub struct PortConfig<A> {
+ pub acceptable_master_list: A,
+ pub delay_mechanism: DelayMechanism,
+ pub announce_interval: Interval,
+ pub announce_receipt_timeout: u8,
+ pub sync_interval: Interval,
+ pub master_only: bool,
+ pub delay_asymmetry: Duration,
+}
Configuration items of the PTP PortDS dataset. Dynamical fields are kept +as part of crate::port::Port.
+acceptable_master_list: A
§delay_mechanism: DelayMechanism
§announce_interval: Interval
§announce_receipt_timeout: u8
§sync_interval: Interval
§master_only: bool
§delay_asymmetry: Duration
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct PtpInstance<F> { /* private fields */ }
A PTP node.
+This object handles the complete running of the PTP protocol once created. +It provides all the logic for both ordinary and boundary clock mode.
+Assuming we already have a network runtime and clock runtime, an ordinary +clock can be run by first creating all the datasets, then creating the port, +then finally setting up the instance and starting it:
+ +let default_ds = DefaultDS::new_ordinary_clock(
+ clock_identity,
+ 128,
+ 128,
+ 0,
+ false,
+ SdoId::new(0).unwrap(),
+);
+let time_properties_ds =
+TimePropertiesDS::new_arbitrary_time(false, false, TimeSource::InternalOscillator);
+let port_ds = PortDS::new(
+ PortIdentity {
+ clock_identity,
+ port_number: 1,
+ },
+ 1,
+ 1,
+ 3,
+ 0,
+ DelayMechanism::E2E,
+ 1,
+);
+let port = Port::new(port_ds, &mut network_runtime, interface_name).await;
+let mut instance = PtpInstance::new_ordinary_clock(
+ default_ds,
+ time_properties_ds,
+ port,
+ local_clock,
+ BasicFilter::new(0.25),
+);
+
+instance.run(&TimerImpl).await;
Add and initialize this port
+We start in the BMCA state because that is convenient
+When providing the port with a different clock than the instance clock, +the caller is responsible for propagating any property changes to this +clock, and for synchronizing this clock with the instance clock as +appropriate based on the ports state.
+pub struct Running<'a> { /* private fields */ }
pub struct SdoId(/* private fields */);
A wrapper type for PTP Sdo Identifiers.
+This is a separate type as sdo identifiers should be in the range 0-4095
+pub struct Time { /* private fields */ }
Time represents a specific moment in time.
+The starting 0 point depends on the timescale being used by PTP, but +for most uses will be the unix epoch.
+Create an instance with the given amount of seconds from the origin
+Create an instance with the given amount of milliseconds from the origin
+Create an instance with the given amount of microseconds from the origin
+Create an instance with the given amount of nanoseconds from the origin
+Create an instance with the given amount of nanoseconds from the origin, +using a fixed point number so the subnanoseconds can be specified as +well
+Get all the nanoseconds that are under a second
++=
operation. Read moreself
and other
) and is used by the <=
+operator. Read more-=
operation. Read morepub struct TimePropertiesDS { /* private fields */ }
A concrete implementation of the PTP Time Properties dataset (IEEE1588-2019 +section 8.2.4
+This dataset describes the timescale currently in use, as well as any +upcoming leap seconds on that timescale.
+Create a Time Properties data set for the PTP timescale.
+This creates a dataset for the default PTP timescale, which is UTC +seconds since the PTP epoch excluding leap seconds. The traceability +properties indicate whether the current clock time and frequency can be +traced back to an internationally recognized standard in the metrology +sense of the word. When in doubt, just set these to false.
+Create a Time Properties data set for an Arbitrary timescale
+The arbitrary timescale can be used when wanting to synchronize multiple +computers using PTP to a timescale that is unrelated to UTC. The +traceability properties indicate whether the current clock time and +frequency can be traced back to an internationally recognized standard +in the metrology sense of the word. When in doubt, just set these to +false.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct TimestampContext { /* private fields */ }
Redirecting to ../../../statime/struct.Duration.html...
+ + + \ No newline at end of file diff --git a/docs/statime/time/instant/struct.Time.html b/docs/statime/time/instant/struct.Time.html new file mode 100644 index 000000000..4ca5936e3 --- /dev/null +++ b/docs/statime/time/instant/struct.Time.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/struct.Time.html...
+ + + \ No newline at end of file diff --git a/docs/statime/time/interval/struct.Interval.html b/docs/statime/time/interval/struct.Interval.html new file mode 100644 index 000000000..419d4e117 --- /dev/null +++ b/docs/statime/time/interval/struct.Interval.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../statime/struct.Interval.html...
+ + + \ No newline at end of file diff --git a/docs/statime/trait.Clock.html b/docs/statime/trait.Clock.html new file mode 100644 index 000000000..6cef49784 --- /dev/null +++ b/docs/statime/trait.Clock.html @@ -0,0 +1,41 @@ +pub trait Clock {
+ type Error: Debug;
+
+ // Required methods
+ fn now(&self) -> Time;
+ fn step_clock(&mut self, offset: Duration) -> Result<Time, Self::Error>;
+ fn set_frequency(&mut self, ppm: f64) -> Result<Time, Self::Error>;
+ fn set_properties(
+ &mut self,
+ time_properties_ds: &TimePropertiesDS
+ ) -> Result<(), Self::Error>;
+}
Clock manipulation and querying interface
+The clock trait is the primary way the PTP stack interfaces with the +system’s clock. It’s implementation should be provided by the user of the +Statime crate, and should provide information on and ways to manipulate the +system’s clock. An implementation of this trait for linux is provided in the +statime-linux crate.
+Note that the clock implementation is responsible for handling leap seconds. +On most operating systems, this will be provided for by the OS, but on some +platforms this may require extra logic.
+Change the current time of the clock by offset. Returns +the time at which the change was applied.
+The applied correction should be as close as possible to +the requested correction. The reported time of the change +should be as close as possible to the time the change was +applied
+Set the frequency of the clock, returning the time +at which the change was applied. The value is in ppm +difference from the clocks base frequency.
+The applied correction should be as close as possible to +the requested correction. The reported time of the change +should be as close as possible to the time the change was +applied
+Adjust the timescale properties of the clock, including +things like the leap indicator, to the extend supported by the +system.
+pub trait Filter {
+ type Config: Clone;
+
+ // Required methods
+ fn new(config: Self::Config) -> Self;
+ fn measurement<C: Clock>(
+ &mut self,
+ m: Measurement,
+ clock: &mut C
+ ) -> FilterUpdate;
+ fn update<C: Clock>(&mut self, clock: &mut C) -> FilterUpdate;
+ fn demobilize<C: Clock>(self, clock: &mut C);
+}
A filter for post-processing time measurements.
+Filters are responsible for dealing with the network noise, and should +average out the input a bit so minor network variations are not immediately +reflected in the synchronization of the clock.
+This crate provides a simple BasicFilter
which is
+suitable for most needs, but users can implement their own if desired.
Put a new measurement in the filter. +The filter can then use this to adjust the clock
+Update initiated through [FilterUpdate::next_update] timeout.
+Handle ending of time synchronization from the source +associated with this filter.
+pub fn libc_timespec_into_instant(spec: timespec) -> Time
Implementation of the abstract clock for the linux platform
+pub struct LinuxClock {
+ pub clock: UnixClock,
+}
clock: UnixClock
source
. Read morepub enum ConfigError {
+ Io(Error),
+ Toml(Error),
+}
pub enum NetworkMode {
+ Ipv4,
+ Ipv6,
+}
source
. Read moreself
and other
values to be equal, and is used
+by ==
.key
and return true
if they are equal.pub struct Config {
+ pub loglevel: String,
+ pub sdo_id: u16,
+ pub domain: u8,
+ pub priority1: u8,
+ pub priority2: u8,
+ pub ports: Vec<PortConfig>,
+}
loglevel: String
§sdo_id: u16
§domain: u8
§priority1: u8
§priority2: u8
§ports: Vec<PortConfig>
key
and return true
if they are equal.pub struct PortConfig {
+ pub interface: InterfaceName,
+ pub acceptable_master_list: Option<Vec<ClockIdentity>>,
+ pub hardware_clock: Option<String>,
+ pub network_mode: NetworkMode,
+ pub announce_interval: i8,
+ pub sync_interval: i8,
+ pub announce_receipt_timeout: u8,
+ pub master_only: bool,
+ pub delay_asymetry: i64,
+ pub delay_mechanism: i8,
+}
interface: InterfaceName
§acceptable_master_list: Option<Vec<ClockIdentity>>
§hardware_clock: Option<String>
§network_mode: NetworkMode
§announce_interval: i8
§sync_interval: i8
§announce_receipt_timeout: u8
§master_only: bool
§delay_asymetry: i64
§delay_mechanism: i8
source
. Read moreself
and other
values to be equal, and is used
+by ==
.key
and return true
if they are equal.pub fn open_ipv4_event_socket(
+ interface: InterfaceName,
+ timestamping: InterfaceTimestampMode
+) -> Result<Socket<SocketAddrV4, Open>>
pub fn open_ipv4_general_socket(
+ interface: InterfaceName
+) -> Result<Socket<SocketAddrV4, Open>>
pub fn open_ipv6_event_socket(
+ interface: InterfaceName,
+ timestamping: InterfaceTimestampMode
+) -> Result<Socket<SocketAddrV6, Open>>
pub fn open_ipv6_general_socket(
+ interface: InterfaceName
+) -> Result<Socket<SocketAddrV6, Open>>
pub fn timestamp_to_time(ts: Timestamp) -> Time
Event and General sockets for linux systems
+pub trait PtpTargetAddress {
+ const PRIMARY_EVENT: Self;
+ const PRIMARY_GENERAL: Self;
+ const PDELAY_EVENT: Self;
+ const PDELAY_GENERAL: Self;
+}
This example network configuration consists of two ordinary clocks, connected to each other through a boundary clock, +all of which are running on Linux:
+ +It should be relatively straightforward to extrapolate for more complicated networks.
+To get PTP up and running for each of the ordinary clocks, install statime-linux
on both of them:
cargo install --git https://github.com/pendulum-project/statime.git --rev 52b17e3 --bin statime-linux
+
+statime-linux
contains a CLI binary that can be used to start ordinary clock instances:
# use ifconfig to find the descriptor corresponding to the interface connected to the boundary clock
+ifconfig
+statime-linux --interface enp1s0f1
+
+Ordinary clocks can also be configured through code. Doing so is very similar to configuring boundary clocks, which is +explained in the next section.
+Setting up a boundary clock is currently only possible through code. Start by creating a new Rust project:
+cargo new boundary_clock
+cd boundary_clock
+
+In Cargo.toml
, add the following dependencies:
[package]
+name = "test-bc"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+clap = { version = "3.1.6", features = ["derive"] }
+log = "0.4.14"
+env_logger = "0.10.0"
+statime = { git = "https://github.com/pendulum-project/statime.git", rev = "52b17e3" }
+statime-linux = { git = "https://github.com/pendulum-project/statime.git", rev = "52b17e3" }
+tokio = { version = "1.27", features = ["full"] }
+
+Then, in src/main.rs
, add the following code:
+use std::env; + +use statime::{ + datastructures::{ + common::{ClockIdentity, PortIdentity, TimeSource}, + datasets::{DefaultDS, DelayMechanism, PortDS, TimePropertiesDS}, + messages::SdoId, + }, + filters::basic::BasicFilter, + port::Port, + ptp_instance::PtpInstance, +}; +use statime_linux::{ + clock::{LinuxClock, LinuxTimer, RawLinuxClock}, + network::linux::{get_clock_id, LinuxRuntime}, +}; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let local_clock = LinuxClock::new(RawLinuxClock::get_realtime_clock()); + let mut network_runtime = LinuxRuntime::new(false, &local_clock); + let clock_identity = ClockIdentity(get_clock_id().expect("Could not get clock identity")); + + let default_ds = + DefaultDS::new_boundary_clock(clock_identity, 2, 128, 128, 0, SdoId::default()); + + let time_properties_ds = + TimePropertiesDS::new_arbitrary_time(false, false, TimeSource::InternalOscillator); + + let port_1_ds = PortDS::new( + PortIdentity { + clock_identity, + port_number: 1, + }, + 1, + 1, + 3, + 0, + DelayMechanism::E2E, + 1, + ); + let interface = env::var("PORT1") + .expect("PORT1 interface descriptor not set") + .parse() + .expect("invalid interface descriptor for PORT1"); + let port_1 = Port::new(port_1_ds, &mut network_runtime, interface).await; + + let port_2_ds = PortDS::new( + PortIdentity { + clock_identity, + port_number: 2, + }, + 1, + 1, + 3, + 0, + DelayMechanism::E2E, + 1, + ); + let interface = env::var("PORT2") + .expect("PORT2 interface descriptor not set") + .parse() + .expect("invalid interface descriptor for PORT2"); + let port_2 = Port::new(port_2_ds, &mut network_runtime, interface).await; + + let mut instance = PtpInstance::new_boundary_clock( + default_ds, + time_properties_ds, + [port_1, port_2], + local_clock, + BasicFilter::new(0.25), + ); + + instance.run(&LinuxTimer).await; +}
This configures two PTP ports, binds them to interfaces described by the environment variables PORT1
and PORT2
, and
+starts a boundary clock PTP instance. To run the binary, execute the following commands:
# use ifconfig to find the descriptors corresponding to the interfaces connected to the ordinary clocks
+ifconfig
+PORT1=enp1s0f0 PORT2=enp1s0f1 cargo run
+
+
+