├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── auto_merge_prs.yml │ ├── bump_version.yml │ ├── commitlint.yml │ ├── github_release.yml │ ├── master.yml │ ├── pr.yml │ ├── rustdoc.yml │ ├── security_audit.yml │ └── tag_release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-BSD ├── LICENSE-MIT ├── README.md ├── codeowners └── src ├── lib.rs ├── prefix.rs └── serialize.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for contributing to the project! 11 | We recommend you check out our ["Contributing to the SAFE Network"](https://github.com/maidsafe/QA/blob/master/CONTRIBUTING.md) guide if you haven't already. 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. Ubuntu 18.04.4] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for contributing to the project! 11 | We recommend you check out our ["Contributing to the SAFE Network"](https://github.com/maidsafe/QA/blob/master/CONTRIBUTING.md) guide if you haven't already. 12 | 13 | **Is your feature request related to a problem? Please describe.** 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 16 | **Describe the solution you'd like** 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/workflows/auto_merge_prs.yml: -------------------------------------------------------------------------------- 1 | # auto merge workflow. 2 | # 3 | # Auto merge PR if commit msg begins with `chore(release):`, 4 | # or if it has been raised by Dependabot. 5 | # Uses https://github.com/ridedott/merge-me-action. 6 | 7 | name: Merge Version Change and Dependabot PRs automatically 8 | 9 | on: pull_request 10 | 11 | jobs: 12 | merge: 13 | runs-on: ubuntu-20.04 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: '0' 18 | 19 | - name: get commit message 20 | run: | 21 | commitmsg=$(git log --format=%s -n 1 ${{ github.event.pull_request.head.sha }}) 22 | echo "commitmsg=${commitmsg}" >> $GITHUB_ENV 23 | 24 | - name: show commit message 25 | run : echo $commitmsg 26 | 27 | - name: Merge Version change PR 28 | if: startsWith( env.commitmsg, 'chore(release):') 29 | uses: ridedott/merge-me-action@81667e6ae186ddbe6d3c3186d27d91afa7475e2c 30 | with: 31 | GITHUB_LOGIN: dirvine 32 | GITHUB_TOKEN: ${{ secrets.MERGE_BUMP_BRANCH_TOKEN }} 33 | MERGE_METHOD: REBASE 34 | 35 | - name: Dependabot Merge 36 | uses: ridedott/merge-me-action@master 37 | with: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | MERGE_METHOD: REBASE 40 | -------------------------------------------------------------------------------- /.github/workflows/bump_version.yml: -------------------------------------------------------------------------------- 1 | name: Version bump and create PR for changes 2 | 3 | on: 4 | # Trigger the workflow on push only for the master branch 5 | push: 6 | branches: 7 | - master 8 | 9 | env: 10 | NODE_ENV: 'development' 11 | 12 | jobs: 13 | update_changelog: 14 | runs-on: ubuntu-20.04 15 | # Dont run if we're on a release commit 16 | if: "!startsWith(github.event.head_commit.message, 'chore(release):')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | fetch-depth: '0' 21 | - name: Bump Version 22 | uses: maidsafe/rust-version-bump-branch-creator@v2 23 | with: 24 | token: ${{ secrets.BRANCH_CREATOR_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Commitlint 2 | on: [pull_request] 3 | 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - uses: wagoid/commitlint-github-action@f114310111fdbd07e99f47f9ca13d62b3ec98372 14 | -------------------------------------------------------------------------------- /.github/workflows/github_release.yml: -------------------------------------------------------------------------------- 1 | name: Create GitHub Release 2 | 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | release: 11 | # only if we have a tag 12 | name: Release 13 | runs-on: ubuntu-20.04 14 | if: "startsWith(github.event.head_commit.message, 'chore(release):')" 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: '0' 20 | 21 | - name: Set tag as env 22 | shell: bash 23 | run: echo "RELEASE_VERSION=$(echo ${GITHUB_REF:10})" >> $GITHUB_ENV 24 | 25 | - name: lets check tag 26 | shell: bash 27 | run: echo ${{ env.RELEASE_VERSION }} 28 | 29 | - name: Generate Changelog 30 | shell: bash 31 | run: awk '/# \[/{c++;p=1}{if(c==2){exit}}p;' CHANGELOG.md > RELEASE-CHANGELOG.txt 32 | - run: cat RELEASE-CHANGELOG.txt 33 | - name: Release generation 34 | uses: softprops/action-gh-release@91409e712cf565ce9eff10c87a8d1b11b81757ae 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.MERGE_BUMP_BRANCH_TOKEN }} 37 | with: 38 | body_path: RELEASE-CHANGELOG.txt 39 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | # Push to master workflow. 2 | # 3 | # Runs when a PR has been merged to the master branch. 4 | # 5 | # 1. Generates a release build. 6 | # 2. If the last commit is a version change, publish. 7 | 8 | name: Master 9 | 10 | on: 11 | push: 12 | branches: 13 | - master 14 | 15 | env: 16 | # Run all cargo commands with --verbose. 17 | CARGO_TERM_VERBOSE: true 18 | RUST_BACKTRACE: 1 19 | 20 | jobs: 21 | build: 22 | name: Build 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | matrix: 26 | os: [ubuntu-latest, windows-latest, macOS-latest] 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Install Rust 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | profile: minimal 33 | toolchain: stable 34 | override: true 35 | 36 | # Cache. 37 | - name: Cargo cache registry, index and build 38 | uses: actions/cache@v2.1.4 39 | with: 40 | path: | 41 | ~/.cargo/registry 42 | ~/.cargo/git 43 | target 44 | key: ${{ runner.os }}-cargo-cache-${{ hashFiles('**/Cargo.lock') }} 45 | 46 | # Make sure the code builds. 47 | - name: Run cargo build 48 | run: cargo build --release --workspace 49 | 50 | # Publish if we're on a release commit 51 | publish: 52 | name: Publish 53 | runs-on: ubuntu-latest 54 | needs: build 55 | if: "startsWith(github.event.head_commit.message, 'chore(release):')" 56 | steps: 57 | - uses: actions/checkout@v2 58 | # checkout with fetch-depth: '0' to be sure to retrieve all commits to look for the semver commit message 59 | with: 60 | fetch-depth: '0' 61 | 62 | # Install Rust 63 | - uses: actions-rs/toolchain@v1 64 | with: 65 | profile: minimal 66 | toolchain: stable 67 | override: true 68 | 69 | # Publish to crates.io. 70 | - name: Cargo Login 71 | run: cargo login ${{ secrets.CRATES_IO_TOKEN }} 72 | 73 | - name: Cargo Publish 74 | run: cargo publish 75 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: [pull_request] 4 | 5 | env: 6 | # Run all cargo commands with --verbose. 7 | CARGO_TERM_VERBOSE: true 8 | RUST_BACKTRACE: 1 9 | # Deny all compiler warnings. 10 | RUSTFLAGS: "-D warnings" 11 | 12 | jobs: 13 | clippy: 14 | if: "!startsWith(github.event.pull_request.title, 'Automated version bump')" 15 | name: clippy & fmt checks 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | components: rustfmt, clippy 25 | 26 | # Cache. 27 | - name: Cargo cache registry, index and build 28 | uses: actions/cache@v2.1.4 29 | with: 30 | path: | 31 | ~/.cargo/registry 32 | ~/.cargo/git 33 | target 34 | key: ${{ runner.os }}-cargo-cache-${{ hashFiles('**/Cargo.lock') }} 35 | 36 | # Check if the code is formatted correctly. 37 | - name: Check formatting 38 | run: cargo fmt --all -- --check 39 | 40 | # Run Clippy. 41 | - name: Clippy checks 42 | run: cargo clippy --all-targets --all-features 43 | 44 | coverage: 45 | if: "!startsWith(github.event.pull_request.title, 'Automated version bump')" 46 | name: Code coverage check 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v2 50 | # Install Rust and required components 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | profile: minimal 54 | toolchain: stable 55 | override: true 56 | 57 | # Cache. 58 | - name: Cargo cache registry, index and build 59 | uses: actions/cache@v2.1.4 60 | with: 61 | path: | 62 | ~/.cargo/registry 63 | ~/.cargo/git 64 | target 65 | key: ${{ runner.os }}-cargo-cache-${{ hashFiles('**/Cargo.lock') }} 66 | 67 | # Run cargo tarpaulin & push result to coveralls.io 68 | - name: rust-tarpaulin code coverage check 69 | uses: actions-rs/tarpaulin@v0.1 70 | with: 71 | args: '-v --release --out Lcov' 72 | - name: Push code coverage results to coveralls.io 73 | uses: coverallsapp/github-action@master 74 | with: 75 | github-token: ${{ secrets.GITHUB_TOKEN }} 76 | parallel: true 77 | path-to-lcov: ./lcov.info 78 | - name: Coveralls Finished 79 | uses: coverallsapp/github-action@master 80 | with: 81 | github-token: ${{ secrets.GITHUB_TOKEN }} 82 | parallel-finished: true 83 | 84 | cargo-udeps: 85 | if: "!startsWith(github.event.pull_request.title, 'Automated version bump')" 86 | name: Unused dependency check 87 | runs-on: ubuntu-latest 88 | steps: 89 | - uses: actions/checkout@v2 90 | # Install Rust and required components 91 | - uses: actions-rs/toolchain@v1 92 | with: 93 | toolchain: nightly 94 | override: true 95 | 96 | - name: Run cargo-udeps 97 | uses: aig787/cargo-udeps-action@v1 98 | with: 99 | version: 'latest' 100 | args: '--all-targets' 101 | 102 | cargo-deny: 103 | if: "!startsWith(github.event.pull_request.title, 'Automated version bump')" 104 | runs-on: ubuntu-latest 105 | steps: 106 | - uses: actions/checkout@v2 107 | 108 | # wget the shared deny.toml file from the QA repo 109 | - shell: bash 110 | run: wget https://raw.githubusercontent.com/maidsafe/QA/master/misc-scripts/deny.toml 111 | 112 | - uses: EmbarkStudios/cargo-deny-action@v1 113 | 114 | test: 115 | if: "!startsWith(github.event.pull_request.title, 'Automated version bump')" 116 | name: Test 117 | runs-on: ${{ matrix.os }} 118 | strategy: 119 | matrix: 120 | os: [ubuntu-latest, windows-latest, macOS-latest] 121 | steps: 122 | - uses: actions/checkout@v2 123 | - name: Install Rust 124 | uses: actions-rs/toolchain@v1 125 | with: 126 | profile: minimal 127 | toolchain: stable 128 | override: true 129 | 130 | # Cache. 131 | - name: Cargo cache registry, index and build 132 | uses: actions/cache@v2.1.4 133 | with: 134 | path: | 135 | ~/.cargo/registry 136 | ~/.cargo/git 137 | target 138 | key: ${{ runner.os }}-cargo-cache-${{ hashFiles('**/Cargo.lock') }} 139 | 140 | # Run the tests. 141 | - name: Cargo test 142 | run: cargo test --all-features --release 143 | 144 | # Test publish using --dry-run. 145 | test-publish: 146 | if: "!startsWith(github.event.pull_request.title, 'Automated version bump')" 147 | name: Test Publish 148 | runs-on: ubuntu-latest 149 | steps: 150 | - uses: actions/checkout@v2 151 | # Install Rust 152 | - uses: actions-rs/toolchain@v1 153 | with: 154 | profile: minimal 155 | toolchain: stable 156 | override: true 157 | 158 | # Cargo publish dry run 159 | - name: Cargo Publish Dry Run 160 | run: cargo publish --dry-run 161 | -------------------------------------------------------------------------------- /.github/workflows/rustdoc.yml: -------------------------------------------------------------------------------- 1 | name: rustdoc 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | env: 8 | CARGO_INCREMENTAL: 0 9 | CARGO_NET_RETRY: 10 10 | RUSTFLAGS: -D warnings 11 | RUSTUP_MAX_RETRIES: 10 12 | 13 | jobs: 14 | rustdoc: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Install Rust toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | profile: minimal 26 | override: true 27 | components: rustfmt, rust-src 28 | 29 | - name: Build Documentation 30 | run: cargo doc --all --no-deps 31 | 32 | - name: Deploy Docs 33 | uses: peaceiris/actions-gh-pages@v3 34 | with: 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | publish_branch: gh-pages 37 | publish_dir: ./target/doc 38 | keep_files: true 39 | -------------------------------------------------------------------------------- /.github/workflows/security_audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | audit: 7 | if: github.repository_owner == 'maidsafe' 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/audit-check@v1 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/tag_release.yml: -------------------------------------------------------------------------------- 1 | name: Tag release commit 2 | 3 | on: 4 | # Trigger the workflow on push only for the master branch 5 | push: 6 | branches: 7 | - master 8 | 9 | env: 10 | NODE_ENV: 'development' 11 | GITHUB_TOKEN: ${{ secrets.BRANCH_CREATOR_TOKEN }} 12 | 13 | jobs: 14 | tag: 15 | runs-on: ubuntu-latest 16 | # Only run on a release commit 17 | if: "startsWith(github.event.head_commit.message, 'chore(release):')" 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: '0' 22 | token: ${{ secrets.BRANCH_CREATOR_TOKEN }} 23 | - run: echo "RELEASE_VERSION=$(git log -1 --pretty=%s)" >> $GITHUB_ENV 24 | # parse out non-tag text 25 | - run: echo "RELEASE_VERSION=$( echo $RELEASE_VERSION | sed 's/chore(release)://' )" >> $GITHUB_ENV 26 | # remove spaces, but add back in `v` to tag, which is needed for standard-version 27 | - run: echo "RELEASE_VERSION=v$(echo $RELEASE_VERSION | tr -d '[:space:]')" >> $GITHUB_ENV 28 | - run: echo $RELEASE_VERSION 29 | - run: git tag $RELEASE_VERSION 30 | 31 | - name: Setup git for push 32 | run: | 33 | git remote add github "$REPO" 34 | git config --local user.email "action@github.com" 35 | git config --local user.name "GitHub Action" 36 | - name: Push tags to master 37 | run: git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY" HEAD:master --tags 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.lock 3 | *.swp 4 | *.rsproj 5 | tags* 6 | *bootstrap.cache 7 | build/ 8 | build-tests/ 9 | target/ 10 | /.idea 11 | /.project 12 | /bin 13 | *.sublime-* 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [5.0.0](https://github.com/maidsafe/xor_name/compare/v4.1.0...v5.0.0) (2022-08-05) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * error instead of panic on too long string 11 | 12 | ### Features 13 | 14 | * different Prefix serialization format ([6ff6d39](https://github.com/maidsafe/xor_name/commit/6ff6d39400179f28580530fa3ee4bdb59db26876)) 15 | * impl Display for Prefix ([d42ba53](https://github.com/maidsafe/xor_name/commit/d42ba5309329be2780fa4efab15532275f6b1bd9)) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * allow prefix strings of more than one byte ([1f05f48](https://github.com/maidsafe/xor_name/commit/1f05f4841a848667cca24fe08c22bde6a3a36d59)) 21 | * error instead of panic on too long string ([73123cb](https://github.com/maidsafe/xor_name/commit/73123cbb0e2a23d30fc6298ad35812209dca27b5)) 22 | 23 | ## [4.1.0](https://github.com/maidsafe/xor_name/compare/v4.0.1...v4.1.0) (2022-08-04) 24 | 25 | 26 | ### Features 27 | 28 | * serialize to string ([9d54992](https://github.com/maidsafe/xor_name/commit/9d54992cdf519d66524fd9fdbedb53780133c183)) 29 | 30 | ### [4.0.1](https://github.com/maidsafe/xor_name/compare/v4.0.0...v4.0.1) (2022-03-18) 31 | 32 | ## [4.0.0](https://github.com/maidsafe/xor_name/compare/v3.1.0...v4.0.0) (2022-03-16) 33 | 34 | 35 | ### ⚠ BREAKING CHANGES 36 | 37 | * public api changed 38 | 39 | * remove OsRng ([c4d64e9](https://github.com/maidsafe/xor_name/commit/c4d64e98556e5c9caff902182c9e840dad869580)) 40 | 41 | ## [3.1.0](https://github.com/maidsafe/xor_name/compare/v3.0.0...v3.1.0) (2021-08-26) 42 | 43 | 44 | ### Features 45 | 46 | * **api:** simplify from content api ([441acc7](https://github.com/maidsafe/xor_name/commit/441acc7269747cff6868adf425cd0be6c12b39e5)) 47 | 48 | ## [3.0.0](https://github.com/maidsafe/xor_name/compare/v2.0.0...v3.0.0) (2021-08-24) 49 | 50 | 51 | ### ⚠ BREAKING CHANGES 52 | 53 | * remove prefix_map 54 | 55 | ### Features 56 | 57 | * remove prefix_map make with_bit public ([efa63e2](https://github.com/maidsafe/xor_name/commit/efa63e26dc3820c6ba1cdeaf270f41030684fa09)) 58 | * use DashMaps for better concurrency ([2ef45f3](https://github.com/maidsafe/xor_name/commit/2ef45f328699ccb8a750b8f0e5788b792414f3c1)) 59 | 60 | ## [2.0.0](https://github.com/maidsafe/xor_name/compare/v1.3.0...v2.0.0) (2021-08-10) 61 | 62 | 63 | ### ⚠ BREAKING CHANGES 64 | 65 | * **prefix-map:** - Expose PrefixMap as public from lib and remove pub prefix_map mod. 66 | - Adapting PrefixMap APIs to the removal of requirement of Borrow Trait for T. 67 | 68 | * **prefix-map:** remove the requirement of Borrow trait for T from PrefixMap ([1e32830](https://github.com/maidsafe/xor_name/commit/1e32830af72ae37f58a9961b8a0c8dde0981b0e0)) 69 | 70 | ## [1.3.0](https://github.com/maidsafe/xor_name/compare/v1.2.1...v1.3.0) (2021-08-06) 71 | 72 | 73 | ### Features 74 | 75 | * insert returns bool ([7e36f9d](https://github.com/maidsafe/xor_name/commit/7e36f9dfeb49765b281625f07ec64fd320c666d2)) 76 | * prefix map ([83be995](https://github.com/maidsafe/xor_name/commit/83be99545a3dda1fdb9d0c13a9d18a757bec8538)) 77 | * remove get_equal_or_ancestor ([4c2c7ed](https://github.com/maidsafe/xor_name/commit/4c2c7ed40db22f14a8548d8bb6e36589a0111165)) 78 | * use BTreeMap add get_matching_prefix make prune pub ([069767c](https://github.com/maidsafe/xor_name/commit/069767ce0e98a86e9b04f8efa2c91225968e022d)) 79 | 80 | ### [1.2.1](https://github.com/maidsafe/xor_name/compare/v1.2.0...v1.2.1) (2021-06-08) 81 | 82 | ## [1.2.0](https://github.com/maidsafe/xor_name/compare/v1.1.12...v1.2.0) (2021-04-19) 83 | 84 | 85 | ### Features 86 | 87 | * Debug output with binary fmt as well ([1382403](https://github.com/maidsafe/xor_name/commit/1382403befe73de1961fcde8ec6cfa042dd36fb0)) 88 | 89 | ### [1.1.12](https://github.com/maidsafe/xor_name/compare/v1.1.11...v1.1.12) (2021-03-03) 90 | 91 | ### [1.1.11](https://github.com/maidsafe/xor_name/compare/v1.1.10...v1.1.11) (2021-02-25) 92 | 93 | ### [1.1.10](https://github.com/maidsafe/xor_name/compare/v1.1.9...v1.1.10) (2021-02-09) 94 | 95 | ### [1.1.9](https://github.com/maidsafe/xor_name/compare/v1.1.8...v1.1.9) (2021-02-03) 96 | 97 | ### [1.1.8](https://github.com/maidsafe/xor_name/compare/v1.1.7...v1.1.8) (2021-02-03) 98 | 99 | ### [1.1.7](https://github.com/maidsafe/xor_name/compare/v1.1.6...v1.1.7) (2021-01-20) 100 | 101 | ### [1.1.6](https://github.com/maidsafe/xor_name/compare/v1.1.5...v1.1.6) (2021-01-14) 102 | 103 | ### [1.1.5](https://github.com/maidsafe/xor_name/compare/v1.1.4...v1.1.5) (2021-01-06) 104 | 105 | ### [1.1.4](https://github.com/maidsafe/xor_name/compare/v1.1.3...v1.1.4) (2020-11-23) 106 | 107 | ### [1.1.3](https://github.com/maidsafe/xor_name/compare/v1.1.2...v1.1.3) (2020-10-09) 108 | 109 | ### [1.1.2](https://github.com/maidsafe/xor_name/compare/v1.1.1...v1.1.2) (2020-10-09) 110 | 111 | ### [1.1.1](https://github.com/maidsafe/xor_name/compare/v1.1.0...v1.1.1) (2020-09-21) 112 | 113 | ### [1.1.0](https://github.com/maidsafe/xor_name/compare/v1.0.0...v1.1.0) (2020-08-18) 114 | * Add in `XorName::random()` functionality 115 | * Use OSRng 116 | 117 | ### [1.0.0](https://github.com/maidsafe/xor_name/compare/0.9.2...v1.0.0) (2020-07-02) 118 | * Make the crate no_std 119 | * Add impl Deref for XorName, remove slice indexing 120 | * Minimise the API surface 121 | * Remove generics 122 | 123 | ### [0.9.2] 124 | * Remove test barrier from the FromStr trait impl for Prefix 125 | 126 | ### [0.9.1] 127 | * Added borrow trait for prefix 128 | 129 | ### [0.9.0] 130 | * Extracted from the routing crate to become standalone (again) 131 | * License details updated to MIT or modified BSD license. 132 | * CI set up on GitHub Actions 133 | 134 | ### [0.1.0] 135 | * Replace CBOR usage with maidsafe_utilites::serialisation. 136 | * Updated dependencies. 137 | 138 | ### [0.0.5] 139 | * Migrate to maidsafe_utilities 0.2.1. 140 | * Make debug hex output lowercase. 141 | 142 | ### [0.0.4] 143 | * Reduce debug output to improve readability. 144 | 145 | ### [0.0.3] 146 | * Add the `with_flipped_bit` and `count_differing_bits` methods. 147 | * Rename `cmp_closeness` to `cmp_distance`. 148 | 149 | ### [0.0.2] 150 | * Rename `bucket_distance` to `bucket_index`. 151 | * Expand documentation. 152 | * Add `XorName::cmp_closeness` ordering method. 153 | 154 | ### [0.0.1] 155 | * Initial implementation 156 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xor_name" 3 | version = "5.0.0" 4 | authors = [ "MaidSafe Developers " ] 5 | description = "Xor Type" 6 | homepage = "http://maidsafe.net" 7 | edition = "2018" 8 | license = "MIT OR BSD-3-Clause" 9 | readme = "README.md" 10 | repository = "https://github.com/maidsafe/xor_name" 11 | 12 | [features] 13 | default = [ "serialize-hex" ] 14 | serialize-hex = [ "hex", "serde_test" ] 15 | 16 | [dependencies] 17 | rand_core = "0.6.3" 18 | 19 | [dependencies.tiny-keccak] 20 | version = "~2.0" 21 | features = [ "sha3" ] 22 | 23 | [dependencies.rand] 24 | version = "~0.8.5" 25 | default-features = false 26 | features = [ "std" ] 27 | 28 | [dependencies.serde] 29 | version = "1.0.113" 30 | default-features = false 31 | features = [ "derive" ] 32 | 33 | [dependencies.serde_test] 34 | version = "1" 35 | optional = true 36 | 37 | [dependencies.hex] 38 | version = "0.4" 39 | optional = true 40 | 41 | [dev-dependencies] 42 | bincode = "1.2.1" 43 | 44 | [dev-dependencies.arrayvec] 45 | version = "~0.5.1" 46 | default-features = false 47 | 48 | [dev-dependencies.rand] 49 | version = "~0.8.5" 50 | default-features = false 51 | features = [ "getrandom", "small_rng" ] 52 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright 2020 MaidSafe.net limited. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020 Maidsafe.net limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xor_name 2 | 3 | XorName is an array that is useful for calculations in DHT 4 | 5 | | [MaidSafe website](http://maidsafe.net) | [SAFE Network Forum](https://safenetforum.org/) | 6 | |:-------:|:-------:| 7 | 8 | ## Serialization 9 | 10 | `XorName` and `Prefix` are serialized into a human-readable hex string, instead of as a `u8` array. This is enabled by default, with the `serialize-hex` feature. This also allows for these structures to be serialised when used as a key in a map like `HashMap`, because most formats only allow keys to be strings, instead of more complex types. 11 | 12 | A struct like this: 13 | ```rust 14 | #[derive(Serialize, Deserialize)] 15 | struct MyStruct { 16 | prefix: Prefix, 17 | xor_name: XorName, 18 | } 19 | ``` 20 | 21 | Will yield this JSON 22 | ```json 23 | { 24 | "prefix": "10001101110001111100101000111001101101111101111010011001", 25 | "xor_name": "8dc7ca39b7de990eb943fd64854776dd85aa82c33a4269693c57b36e0749ed8f" 26 | } 27 | ``` 28 | 29 | instead of 30 | ```json 31 | { 32 | "prefix": { 33 | "bit_count": 56, 34 | "name": [141,199,202,57,183,222,153,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] 35 | }, 36 | "xor_name": [141,199,202,57,183,222,153,14,185,67,253,100,133,71,118,221,133,170,130,195,58,66,105,105,60,87,179,110,7,73,237,143] 37 | } 38 | ``` 39 | 40 | ## License 41 | 42 | This SAFE Network library is dual-licensed under the Modified BSD ([LICENSE-BSD](LICENSE-BSD) https://opensource.org/licenses/BSD-3-Clause) or the MIT license ([LICENSE-MIT](LICENSE-MIT) https://opensource.org/licenses/MIT) at your option. 43 | 44 | ## Contributing 45 | 46 | Want to contribute? Great :tada: 47 | 48 | There are many ways to give back to the project, whether it be writing new code, fixing bugs, or just reporting errors. All forms of contributions are encouraged! 49 | 50 | For instructions on how to contribute, see our [Guide to contributing](https://github.com/maidsafe/QA/blob/master/CONTRIBUTING.md). 51 | -------------------------------------------------------------------------------- /codeowners: -------------------------------------------------------------------------------- 1 | * @maidsafe/backend_codeowners 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | //! # xor_name 11 | //! 12 | //! TODO requires further documentation. 13 | 14 | #![doc( 15 | html_logo_url = "https://raw.githubusercontent.com/maidsafe/QA/master/Images/maidsafe_logo.png", 16 | html_favicon_url = "http://maidsafe.net/img/favicon.ico", 17 | html_root_url = "http://maidsafe.github.io/xor_name" 18 | )] 19 | // For explanation of lint checks, run `rustc -W help` or see 20 | // https://github.com/maidsafe/QA/blob/master/Documentation/Rust%20Lint%20Checks.md 21 | #![forbid(mutable_transmutes, no_mangle_const_items, unknown_crate_types)] 22 | #![deny( 23 | deprecated, 24 | improper_ctypes, 25 | missing_docs, 26 | non_shorthand_field_patterns, 27 | overflowing_literals, 28 | stable_features, 29 | unconditional_recursion, 30 | unknown_lints, 31 | unsafe_code, 32 | unused, 33 | unused_allocation, 34 | unused_attributes, 35 | unused_comparisons, 36 | unused_features, 37 | unused_parens, 38 | while_true, 39 | warnings 40 | )] 41 | #![warn( 42 | trivial_casts, 43 | trivial_numeric_casts, 44 | unused_extern_crates, 45 | unused_import_braces, 46 | unused_qualifications, 47 | unused_results 48 | )] 49 | #![allow( 50 | box_pointers, 51 | missing_copy_implementations, 52 | missing_debug_implementations, 53 | variant_size_differences 54 | )] 55 | 56 | use core::{cmp::Ordering, fmt, ops}; 57 | pub use prefix::Prefix; 58 | pub use rand; 59 | use rand::distributions::{Distribution, Standard}; 60 | use tiny_keccak::{Hasher, Sha3}; 61 | 62 | /// Creates XorName with the given leading bytes and the rest filled with zeroes. 63 | #[macro_export] 64 | macro_rules! xor_name { 65 | ($($byte:expr),* $(,)?) => {{ 66 | let mut name = $crate::XorName::default(); 67 | let mut index = 0; 68 | 69 | #[allow(unused_assignments)] 70 | { 71 | $( 72 | name.0[index] = $byte; 73 | index += 1; 74 | )* 75 | } 76 | 77 | name 78 | }} 79 | } 80 | 81 | // No-std replacement for std::format! macro which returns `ArrayString` instead of `String`. The 82 | // capacity of the returned `ArrayString` needs to explicitly given as the first argument. 83 | #[cfg(test)] 84 | macro_rules! format { 85 | ($capacity:expr, $($arg:tt)*) => {{ 86 | let mut output = arrayvec::ArrayString::<[_; $capacity]>::new(); 87 | core::fmt::write(&mut output, core::format_args!($($arg)*)).expect("insufficient ArrayString capacity"); 88 | output 89 | }} 90 | } 91 | 92 | mod prefix; 93 | #[cfg(feature = "serialize-hex")] 94 | mod serialize; 95 | 96 | /// Constant byte length of `XorName`. 97 | pub const XOR_NAME_LEN: usize = 32; 98 | 99 | /// A 256-bit number, viewed as a point in XOR space. 100 | /// 101 | /// This wraps an array of 32 bytes, i. e. a number between 0 and 2256 - 1. 102 | /// 103 | /// XOR space is the space of these numbers, with the [XOR metric][1] as a notion of distance, 104 | /// i. e. the points with IDs `x` and `y` are considered to have distance `x xor y`. 105 | /// 106 | /// [1]: https://en.wikipedia.org/wiki/Kademlia#System_details 107 | #[derive(Eq, Copy, Clone, Default, Hash, Ord, PartialEq, PartialOrd)] 108 | #[cfg_attr( 109 | not(feature = "serialize-hex"), 110 | derive(serde::Serialize, serde::Deserialize) 111 | )] 112 | pub struct XorName(pub [u8; XOR_NAME_LEN]); 113 | 114 | impl XorName { 115 | /// Generate a XorName for the given content. 116 | pub fn from_content(content: &[u8]) -> Self { 117 | Self::from_content_parts(&[content]) 118 | } 119 | 120 | /// Generate a XorName for the given content (for content-addressable-storage) 121 | pub fn from_content_parts(content_parts: &[&[u8]]) -> Self { 122 | let mut sha3 = Sha3::v256(); 123 | for part in content_parts { 124 | sha3.update(part); 125 | } 126 | let mut hash = [0u8; XOR_NAME_LEN]; 127 | sha3.finalize(&mut hash); 128 | Self(hash) 129 | } 130 | 131 | /// Generate a random XorName 132 | pub fn random(rng: &mut T) -> Self { 133 | let mut xor = [0u8; XOR_NAME_LEN]; 134 | rng.fill(&mut xor); 135 | Self(xor) 136 | } 137 | 138 | /// Returns `true` if the `i`-th bit is `1`. 139 | pub fn bit(&self, i: u8) -> bool { 140 | let index = i / 8; 141 | let pow_i = 1 << (7 - (i % 8)); 142 | self[index as usize] & pow_i != 0 143 | } 144 | 145 | /// Compares the distance of the arguments to `self`. Returns `Less` if `lhs` is closer, 146 | /// `Greater` if `rhs` is closer, and `Equal` if `lhs == rhs`. (The XOR distance can only be 147 | /// equal if the arguments are equal.) 148 | pub fn cmp_distance(&self, lhs: &Self, rhs: &Self) -> Ordering { 149 | for i in 0..XOR_NAME_LEN { 150 | if lhs[i] != rhs[i] { 151 | return Ord::cmp(&(lhs[i] ^ self[i]), &(rhs[i] ^ self[i])); 152 | } 153 | } 154 | Ordering::Equal 155 | } 156 | 157 | /// Returns a copy of `self`, with the `i`-th bit set to `bit`. 158 | /// 159 | /// If `i` exceeds the number of bits in `self`, an unmodified copy of `self` is returned. 160 | pub fn with_bit(mut self, i: u8, bit: bool) -> Self { 161 | if i as usize >= XOR_NAME_LEN * 8 { 162 | return self; 163 | } 164 | let pow_i = 1 << (7 - i % 8); 165 | if bit { 166 | self.0[i as usize / 8] |= pow_i; 167 | } else { 168 | self.0[i as usize / 8] &= !pow_i; 169 | } 170 | self 171 | } 172 | 173 | /// Returns a copy of `self`, with the `i`-th bit flipped. 174 | /// 175 | /// If `i` exceeds the number of bits in `self`, an unmodified copy of `self` is returned. 176 | fn with_flipped_bit(mut self, i: u8) -> Self { 177 | if i as usize >= XOR_NAME_LEN * 8 { 178 | return self; 179 | } 180 | self.0[i as usize / 8] ^= 1 << (7 - i % 8); 181 | self 182 | } 183 | 184 | /// Returns a copy of self with first `n` bits preserved, and remaining bits 185 | /// set to 0 (val == false) or 1 (val == true). 186 | fn set_remaining(mut self, n: u8, val: bool) -> Self { 187 | for (i, x) in self.0.iter_mut().enumerate() { 188 | let i = i as u8; 189 | 190 | if n <= i * 8 { 191 | *x = if val { !0 } else { 0 }; 192 | } else if n < (i + 1) * 8 { 193 | let mask = !0 >> (n - i * 8); 194 | if val { 195 | *x |= mask 196 | } else { 197 | *x &= !mask 198 | } 199 | } 200 | // else n >= (i+1) * bits: nothing to do 201 | } 202 | self 203 | } 204 | 205 | /// Returns the length of the common prefix with the `other` name; e. g. 206 | /// the when `other = 11110000` and `self = 11111111` this is 4. 207 | fn common_prefix(&self, other: &Self) -> usize { 208 | for byte_index in 0..XOR_NAME_LEN { 209 | if self[byte_index] != other[byte_index] { 210 | return (byte_index * 8) 211 | + (self[byte_index] ^ other[byte_index]).leading_zeros() as usize; 212 | } 213 | } 214 | 8 * XOR_NAME_LEN 215 | } 216 | } 217 | 218 | impl fmt::Debug for XorName { 219 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 220 | write!( 221 | formatter, 222 | "{:02x}{:02x}{:02x}({:08b})..", 223 | self[0], self[1], self[2], self 224 | ) 225 | } 226 | } 227 | 228 | impl fmt::Display for XorName { 229 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 230 | write!(formatter, "{:02x}{:02x}{:02x}..", self[0], self[1], self[2]) 231 | } 232 | } 233 | 234 | impl fmt::Binary for XorName { 235 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 236 | if let Some(width) = formatter.width() { 237 | let whole_bytes = width / 8; 238 | let remaining_bits = width % 8; 239 | 240 | for byte in &self[..whole_bytes] { 241 | write!(formatter, "{:08b}", byte)? 242 | } 243 | 244 | for bit in 0..remaining_bits { 245 | write!(formatter, "{}", (self[whole_bytes] >> (7 - bit)) & 1)?; 246 | } 247 | 248 | if formatter.alternate() && whole_bytes < XOR_NAME_LEN - 1 { 249 | write!(formatter, "..")?; 250 | } 251 | } else { 252 | for byte in &self[..] { 253 | write!(formatter, "{:08b}", byte)? 254 | } 255 | } 256 | Ok(()) 257 | } 258 | } 259 | 260 | impl fmt::LowerHex for XorName { 261 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 262 | let bytes = formatter.width().unwrap_or(2 * XOR_NAME_LEN) / 2; 263 | 264 | for byte in &self[..bytes] { 265 | write!(formatter, "{:02x}", byte)?; 266 | } 267 | 268 | if formatter.alternate() && bytes < XOR_NAME_LEN { 269 | write!(formatter, "..")?; 270 | } 271 | 272 | Ok(()) 273 | } 274 | } 275 | 276 | impl fmt::UpperHex for XorName { 277 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 278 | let bytes = formatter.width().unwrap_or(2 * XOR_NAME_LEN) / 2; 279 | 280 | for byte in &self[..bytes] { 281 | write!(formatter, "{:02X}", byte)?; 282 | } 283 | 284 | if formatter.alternate() && bytes < XOR_NAME_LEN { 285 | write!(formatter, "..")?; 286 | } 287 | 288 | Ok(()) 289 | } 290 | } 291 | 292 | impl Distribution for Standard { 293 | fn sample(&self, rng: &mut R) -> XorName { 294 | let mut name = XorName::default(); 295 | rng.fill(&mut name.0[..]); 296 | name 297 | } 298 | } 299 | 300 | impl ops::Not for XorName { 301 | type Output = Self; 302 | 303 | fn not(mut self) -> Self { 304 | for byte in &mut self.0 { 305 | *byte = !*byte; 306 | } 307 | self 308 | } 309 | } 310 | 311 | impl AsRef for XorName { 312 | fn as_ref(&self) -> &Self { 313 | self 314 | } 315 | } 316 | 317 | impl AsRef<[u8]> for XorName { 318 | fn as_ref(&self) -> &[u8] { 319 | &self.0[..] 320 | } 321 | } 322 | 323 | impl ops::Deref for XorName { 324 | type Target = [u8]; 325 | 326 | fn deref(&self) -> &Self::Target { 327 | &self.0[..] 328 | } 329 | } 330 | 331 | #[cfg(test)] 332 | mod tests { 333 | use super::*; 334 | use bincode::{deserialize, serialize}; 335 | use rand::{rngs::SmallRng, Rng, SeedableRng}; 336 | 337 | #[test] 338 | fn create_random_xorname() { 339 | let mut rng = SmallRng::from_entropy(); 340 | let xorname: XorName = XorName::random(&mut rng); 341 | let xorname2: XorName = XorName::random(&mut rng); 342 | 343 | assert_ne!(xorname, xorname2); 344 | } 345 | 346 | #[test] 347 | fn serialisation_xor_name() { 348 | let mut rng = SmallRng::from_entropy(); 349 | let obj_before: XorName = XorName::random(&mut rng); 350 | let data = serialize(&obj_before).unwrap(); 351 | assert_eq!(data.len(), XOR_NAME_LEN); 352 | let obj_after: XorName = deserialize(&data).unwrap(); 353 | assert_eq!(obj_before, obj_after); 354 | } 355 | 356 | #[test] 357 | #[allow(clippy::eq_op, clippy::nonminimal_bool)] 358 | fn xor_name_ord() { 359 | let type1: XorName = XorName([1u8; XOR_NAME_LEN]); 360 | let type2: XorName = XorName([2u8; XOR_NAME_LEN]); 361 | assert_eq!(Ord::cmp(&type1, &type1), Ordering::Equal); 362 | assert_eq!(Ord::cmp(&type1, &type2), Ordering::Less); 363 | assert_eq!(Ord::cmp(&type2, &type1), Ordering::Greater); 364 | assert!(type1 < type2); 365 | assert!(type1 <= type2); 366 | assert!(type1 <= type1); 367 | assert!(type2 > type1); 368 | assert!(type2 >= type1); 369 | assert!(type1 >= type1); 370 | assert!(!(type2 < type1)); 371 | assert!(!(type2 <= type1)); 372 | assert!(!(type1 > type2)); 373 | assert!(!(type1 >= type2)); 374 | } 375 | 376 | #[test] 377 | #[allow(clippy::nonminimal_bool)] 378 | fn xor_name_equal_assertion() { 379 | let mut rng = SmallRng::from_entropy(); 380 | let type1: XorName = rng.gen(); 381 | let type1_clone = type1; 382 | let type2: XorName = rng.gen(); 383 | assert_eq!(type1, type1_clone); 384 | assert!(!(type1 != type1_clone)); 385 | assert_ne!(type1, type2); 386 | } 387 | 388 | #[test] 389 | fn format_debug() { 390 | assert_eq!( 391 | &format!(18, "{:?}", xor_name!(0x01, 0x23, 0x45, 0x67)), 392 | "012345(00000001).." 393 | ); 394 | assert_eq!( 395 | &format!(18, "{:?}", xor_name!(0x89, 0xab, 0xcd, 0xdf)), 396 | "89abcd(10001001).." 397 | ); 398 | } 399 | 400 | #[test] 401 | fn format_hex() { 402 | assert_eq!( 403 | &format!(64, "{:x}", xor_name!(0x01, 0x23, 0xab)), 404 | "0123ab0000000000000000000000000000000000000000000000000000000000" 405 | ); 406 | assert_eq!(&format!(2, "{:2x}", xor_name!(0x01, 0x23, 0xab)), "01"); 407 | assert_eq!(&format!(4, "{:4x}", xor_name!(0x01, 0x23, 0xab)), "0123"); 408 | assert_eq!(&format!(6, "{:6x}", xor_name!(0x01, 0x23, 0xab)), "0123ab"); 409 | assert_eq!( 410 | &format!(8, "{:8x}", xor_name!(0x01, 0x23, 0xab)), 411 | "0123ab00" 412 | ); 413 | assert_eq!( 414 | &format!(10, "{:10x}", xor_name!(0x01, 0x23, 0xab)), 415 | "0123ab0000" 416 | ); 417 | assert_eq!( 418 | &format!(8, "{:8X}", xor_name!(0x01, 0x23, 0xab)), 419 | "0123AB00" 420 | ); 421 | 422 | assert_eq!( 423 | &format!(8, "{:#6x}", xor_name!(0x01, 0x23, 0xab)), 424 | "0123ab.." 425 | ); 426 | 427 | // odd widths are truncated to nearest even 428 | assert_eq!(&format!(2, "{:3x}", xor_name!(0x01, 0x23, 0xab)), "01"); 429 | assert_eq!(&format!(4, "{:5x}", xor_name!(0x01, 0x23, 0xab)), "0123"); 430 | } 431 | 432 | #[test] 433 | fn format_binary() { 434 | assert_eq!( 435 | &format!(256, "{:b}", xor_name!(0b00001111, 0b01010101)), 436 | "00001111010101010000000000000000000000000000000000000000000000000000000000000000000000\ 437 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ 438 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 439 | ); 440 | assert_eq!(&format!(1, "{:1b}", xor_name!(0b00001111, 0b01010101)), "0"); 441 | assert_eq!( 442 | &format!(2, "{:2b}", xor_name!(0b00001111, 0b01010101)), 443 | "00" 444 | ); 445 | assert_eq!( 446 | &format!(3, "{:3b}", xor_name!(0b00001111, 0b01010101)), 447 | "000" 448 | ); 449 | assert_eq!( 450 | &format!(4, "{:4b}", xor_name!(0b00001111, 0b01010101)), 451 | "0000" 452 | ); 453 | assert_eq!( 454 | &format!(5, "{:5b}", xor_name!(0b00001111, 0b01010101)), 455 | "00001" 456 | ); 457 | assert_eq!( 458 | &format!(6, "{:6b}", xor_name!(0b00001111, 0b01010101)), 459 | "000011" 460 | ); 461 | assert_eq!( 462 | &format!(7, "{:7b}", xor_name!(0b00001111, 0b01010101)), 463 | "0000111" 464 | ); 465 | assert_eq!( 466 | &format!(8, "{:8b}", xor_name!(0b00001111, 0b01010101)), 467 | "00001111" 468 | ); 469 | assert_eq!( 470 | &format!(9, "{:9b}", xor_name!(0b00001111, 0b01010101)), 471 | "000011110" 472 | ); 473 | assert_eq!( 474 | &format!(10, "{:10b}", xor_name!(0b00001111, 0b01010101)), 475 | "0000111101" 476 | ); 477 | assert_eq!( 478 | &format!(16, "{:16b}", xor_name!(0b00001111, 0b01010101)), 479 | "0000111101010101" 480 | ); 481 | assert_eq!( 482 | &format!(10, "{:#8b}", xor_name!(0b00001111, 0b01010101)), 483 | "00001111.." 484 | ); 485 | } 486 | 487 | #[test] 488 | fn with_flipped_bit() { 489 | let mut rng = SmallRng::from_entropy(); 490 | let name: XorName = rng.gen(); 491 | for i in 0..18 { 492 | assert_eq!(i, name.common_prefix(&name.with_flipped_bit(i as u8))); 493 | } 494 | for i in 0..10 { 495 | assert_eq!( 496 | 19 * i, 497 | name.common_prefix(&name.with_flipped_bit(19 * i as u8)) 498 | ); 499 | } 500 | } 501 | 502 | #[test] 503 | fn common_prefix() { 504 | assert_eq!( 505 | 0, 506 | xor_name!(0b00000000).common_prefix(&xor_name!(0b10000000)) 507 | ); 508 | assert_eq!( 509 | 3, 510 | xor_name!(0b11100000).common_prefix(&xor_name!(0b11111111)) 511 | ); 512 | assert_eq!( 513 | 5, 514 | xor_name!(0b10101010).common_prefix(&xor_name!(0b10101111)) 515 | ); 516 | assert_eq!( 517 | 0, 518 | xor_name!(0, 0, 0, 0).common_prefix(&xor_name!(128, 0, 0, 0)) 519 | ); 520 | assert_eq!( 521 | 11, 522 | xor_name!(0, 10, 0, 0).common_prefix(&xor_name!(0, 16, 0, 0)) 523 | ); 524 | assert_eq!( 525 | 31, 526 | xor_name!(1, 2, 3, 4).common_prefix(&xor_name!(1, 2, 3, 5)) 527 | ); 528 | assert_eq!( 529 | 256, 530 | xor_name!(1, 2, 3, 4).common_prefix(&xor_name!(1, 2, 3, 4)) 531 | ); 532 | } 533 | 534 | #[test] 535 | fn cmp_distance() { 536 | assert_eq!( 537 | xor_name!(42).cmp_distance(&xor_name!(13), &xor_name!(13)), 538 | Ordering::Equal, 539 | ); 540 | assert_eq!( 541 | xor_name!(42).cmp_distance(&xor_name!(44), &xor_name!(45)), 542 | Ordering::Less, 543 | ); 544 | assert_eq!( 545 | xor_name!(42).cmp_distance(&xor_name!(45), &xor_name!(44)), 546 | Ordering::Greater, 547 | ); 548 | assert_eq!( 549 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(2, 3, 4, 5), &xor_name!(2, 3, 4, 5)), 550 | Ordering::Equal, 551 | ); 552 | assert_eq!( 553 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(2, 2, 4, 5), &xor_name!(2, 3, 6, 5)), 554 | Ordering::Less, 555 | ); 556 | assert_eq!( 557 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(2, 3, 6, 5), &xor_name!(2, 2, 4, 5)), 558 | Ordering::Greater, 559 | ); 560 | assert_eq!( 561 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(1, 2, 3, 8), &xor_name!(1, 2, 8, 4)), 562 | Ordering::Less, 563 | ); 564 | assert_eq!( 565 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(1, 2, 8, 4), &xor_name!(1, 2, 3, 8)), 566 | Ordering::Greater, 567 | ); 568 | assert_eq!( 569 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(1, 2, 7, 4), &xor_name!(1, 2, 6, 4)), 570 | Ordering::Less, 571 | ); 572 | assert_eq!( 573 | xor_name!(1, 2, 3, 4).cmp_distance(&xor_name!(1, 2, 6, 4), &xor_name!(1, 2, 7, 4)), 574 | Ordering::Greater, 575 | ); 576 | } 577 | 578 | #[test] 579 | fn bit() { 580 | assert!(!xor_name!(0b00101000).bit(0)); 581 | assert!(xor_name!(0b00101000).bit(2)); 582 | assert!(!xor_name!(0b00101000).bit(3)); 583 | assert!(xor_name!(2, 128, 1, 0).bit(6)); 584 | assert!(xor_name!(2, 128, 1, 0).bit(8)); 585 | assert!(xor_name!(2, 128, 1, 0).bit(23)); 586 | assert!(!xor_name!(2, 128, 1, 0).bit(7)); 587 | assert!(!xor_name!(2, 128, 1, 0).bit(9)); 588 | assert!(!xor_name!(2, 128, 1, 0).bit(5)); 589 | assert!(!xor_name!(2, 128, 1, 0).bit(22)); 590 | assert!(!xor_name!(2, 128, 1, 0).bit(24)); 591 | } 592 | 593 | #[test] 594 | fn set_remaining() { 595 | assert_eq!( 596 | xor_name!(0b10011011).set_remaining(5, false), 597 | xor_name!(0b10011000) 598 | ); 599 | assert_eq!( 600 | xor_name!(0b11111111).set_remaining(2, false), 601 | xor_name!(0b11000000) 602 | ); 603 | assert_eq!( 604 | xor_name!(0b00000000).set_remaining(4, true), 605 | xor_name!( 606 | 0b00001111, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 607 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 608 | 255 609 | ) 610 | ); 611 | assert_eq!( 612 | xor_name!(13, 112, 9, 1).set_remaining(0, false), 613 | xor_name!(0, 0, 0, 0) 614 | ); 615 | assert_eq!( 616 | xor_name!(13, 112, 9, 1).set_remaining(100, false), 617 | xor_name!(13, 112, 9, 1) 618 | ); 619 | assert_eq!( 620 | xor_name!(13, 112, 9, 1).set_remaining(10, false), 621 | xor_name!(13, 64, 0, 0) 622 | ); 623 | assert_eq!( 624 | xor_name!(13, 112, 9, 1).set_remaining(10, true), 625 | xor_name!( 626 | 13, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 627 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 628 | ) 629 | ); 630 | } 631 | 632 | #[test] 633 | fn xor_name_macro() { 634 | let mut rng = SmallRng::from_entropy(); 635 | 636 | for _ in 0..100 { 637 | let byte = rng.gen(); 638 | assert_eq!(&xor_name!(byte)[..1], &[byte]); 639 | } 640 | 641 | for _ in 0..100 { 642 | let byte0 = rng.gen(); 643 | let byte1 = rng.gen(); 644 | assert_eq!(&xor_name!(byte0, byte1)[..2], &[byte0, byte1]); 645 | } 646 | 647 | for _ in 0..100 { 648 | let byte0 = rng.gen(); 649 | let byte1 = rng.gen(); 650 | let byte2 = rng.gen(); 651 | assert_eq!(&xor_name!(byte0, byte1, byte2)[..3], &[byte0, byte1, byte2]); 652 | } 653 | } 654 | 655 | #[test] 656 | fn conversion_from_u64() { 657 | assert_eq!( 658 | &from_u64(0x0123456789abcdef)[XOR_NAME_LEN - 8..], 659 | &[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef] 660 | ); 661 | } 662 | 663 | #[test] 664 | fn xor_name_from_content() { 665 | let alpha_1 = XorName::from_content_parts(&[b"abcdefg", b"hijk"]); 666 | let alpha_2 = XorName::from_content_parts(&[b"abcdefg", b"hijk"]); 667 | let alpha_3 = XorName::from_content(b"abcdefg"); 668 | 669 | assert_eq!(alpha_1, alpha_2); 670 | assert_ne!(alpha_1, alpha_3); 671 | } 672 | 673 | #[test] 674 | fn xor_name_from_content_is_agnostic_to_where_content_parts_splits() { 675 | let alpha_1 = XorName::from_content_parts(&[b"abcdefg", b"hijk"]); 676 | let alpha_2 = XorName::from_content(b"abcdefghijk"); 677 | assert_eq!(alpha_1, alpha_2); 678 | } 679 | 680 | // Create a `XorName` with the 8 trailing bytes equal to `x` (in big endian order) and the rest 681 | // filled with zeroes. 682 | fn from_u64(x: u64) -> XorName { 683 | let mut name = XorName::default(); 684 | name.0[XOR_NAME_LEN - 8..].copy_from_slice(&x.to_be_bytes()); 685 | name 686 | } 687 | } 688 | -------------------------------------------------------------------------------- /src/prefix.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | use crate::{XorName, XOR_NAME_LEN}; 11 | use core::{ 12 | cmp::{self, Ordering}, 13 | fmt::{Binary, Debug, Display, Formatter, Result as FmtResult}, 14 | hash::{Hash, Hasher}, 15 | ops::RangeInclusive, 16 | str::FromStr, 17 | }; 18 | 19 | /// A section prefix, i.e. a sequence of bits specifying the part of the network's name space 20 | /// consisting of all names that start with this sequence. 21 | #[derive(Clone, Copy, Default, Eq)] 22 | #[cfg_attr( 23 | not(feature = "serialize-hex"), 24 | derive(serde::Serialize, serde::Deserialize) 25 | )] 26 | pub struct Prefix { 27 | pub(crate) bit_count: u16, 28 | pub(crate) name: XorName, 29 | } 30 | 31 | impl Prefix { 32 | /// Creates a new `Prefix` with the first `bit_count` bits of `name`. Insignificant bits are all 33 | /// set to 0. 34 | pub fn new(bit_count: usize, name: XorName) -> Self { 35 | Prefix { 36 | bit_count: bit_count.min(8 * XOR_NAME_LEN) as u16, 37 | name: name.set_remaining(bit_count as u8, false), 38 | } 39 | } 40 | 41 | /// Returns the name of this prefix. 42 | pub fn name(&self) -> XorName { 43 | self.name 44 | } 45 | 46 | /// Returns `self` with an appended bit: `0` if `bit` is `false`, and `1` if `bit` is `true`. If 47 | /// `self.bit_count` is already at the maximum for this type, then an unmodified copy of `self` 48 | /// is returned. 49 | pub fn pushed(mut self, bit: bool) -> Self { 50 | if self.bit_count < 8 * XOR_NAME_LEN as u16 { 51 | self.name = self.name.with_bit(self.bit_count() as u8, bit); 52 | self.bit_count += 1; 53 | } 54 | 55 | self 56 | } 57 | 58 | /// Returns a prefix copying the first `bitcount() - 1` bits from `self`, 59 | /// or `self` if it is already empty. 60 | pub fn popped(mut self) -> Self { 61 | if self.bit_count > 0 { 62 | self.bit_count -= 1; 63 | // unused bits should be zero: 64 | self.name = self.name.with_bit(self.bit_count() as u8, false); 65 | } 66 | self 67 | } 68 | 69 | /// Returns the number of bits in the prefix. 70 | pub fn bit_count(&self) -> usize { 71 | self.bit_count as usize 72 | } 73 | 74 | /// Returns `true` if this is the empty prefix, with no bits. 75 | pub fn is_empty(&self) -> bool { 76 | self.bit_count == 0 77 | } 78 | 79 | /// Returns `true` if `self` is a prefix of `other` or vice versa. 80 | pub fn is_compatible(&self, other: &Self) -> bool { 81 | let i = self.name.common_prefix(&other.name); 82 | i >= self.bit_count() || i >= other.bit_count() 83 | } 84 | 85 | /// Returns `true` if `other` is compatible but strictly shorter than `self`. 86 | pub fn is_extension_of(&self, other: &Self) -> bool { 87 | let i = self.name.common_prefix(&other.name); 88 | i >= other.bit_count() && self.bit_count() > other.bit_count() 89 | } 90 | 91 | /// Returns `true` if the `other` prefix differs in exactly one bit from this one. 92 | pub fn is_neighbour(&self, other: &Self) -> bool { 93 | let i = self.name.common_prefix(&other.name); 94 | if i >= self.bit_count() || i >= other.bit_count() { 95 | false 96 | } else { 97 | let j = self 98 | .name 99 | .with_flipped_bit(i as u8) 100 | .common_prefix(&other.name); 101 | j >= self.bit_count() || j >= other.bit_count() 102 | } 103 | } 104 | 105 | /// Returns the number of common leading bits with the input name, capped with prefix length. 106 | pub fn common_prefix(&self, name: &XorName) -> usize { 107 | cmp::min(self.bit_count(), self.name.common_prefix(name)) 108 | } 109 | 110 | /// Returns `true` if this is a prefix of the given `name`. 111 | pub fn matches(&self, name: &XorName) -> bool { 112 | self.name.common_prefix(name) >= self.bit_count() 113 | } 114 | 115 | /// Compares the distance of `self` and `other` to `target`. Returns `Less` if `self` is closer, 116 | /// `Greater` if `other` is closer, and compares the prefix directly if of equal distance 117 | /// (this is to make sorting deterministic). 118 | pub fn cmp_distance(&self, other: &Self, target: &XorName) -> Ordering { 119 | if self.is_compatible(other) { 120 | // Note that if bit_counts are equal, prefixes are also equal since 121 | // one is a prefix of the other (is_compatible). 122 | Ord::cmp(&self.bit_count, &other.bit_count) 123 | } else { 124 | Ord::cmp( 125 | &other.name.common_prefix(target), 126 | &self.name.common_prefix(target), 127 | ) 128 | } 129 | } 130 | 131 | /// Compares the prefixes using breadth-first order. That is, shorter prefixes are ordered 132 | /// before longer. This is in contrast with the default `Ord` impl of `Prefix` which uses 133 | /// depth-first order. 134 | pub fn cmp_breadth_first(&self, other: &Self) -> Ordering { 135 | self.bit_count 136 | .cmp(&other.bit_count) 137 | .then_with(|| self.name.cmp(&other.name)) 138 | } 139 | 140 | /// Returns the smallest name matching the prefix 141 | pub fn lower_bound(&self) -> XorName { 142 | if self.bit_count() < 8 * XOR_NAME_LEN { 143 | self.name.set_remaining(self.bit_count() as u8, false) 144 | } else { 145 | self.name 146 | } 147 | } 148 | 149 | /// Returns the largest name matching the prefix 150 | pub fn upper_bound(&self) -> XorName { 151 | if self.bit_count() < 8 * XOR_NAME_LEN { 152 | self.name.set_remaining(self.bit_count() as u8, true) 153 | } else { 154 | self.name 155 | } 156 | } 157 | 158 | /// Inclusive range from lower_bound to upper_bound 159 | pub fn range_inclusive(&self) -> RangeInclusive { 160 | RangeInclusive::new(self.lower_bound(), self.upper_bound()) 161 | } 162 | 163 | /// Returns whether the namespace defined by `self` is covered by prefixes in the `prefixes` 164 | /// set 165 | pub fn is_covered_by<'a, I>(&self, prefixes: I) -> bool 166 | where 167 | I: IntoIterator + Clone, 168 | { 169 | let max_prefix_len = prefixes 170 | .clone() 171 | .into_iter() 172 | .map(Self::bit_count) 173 | .max() 174 | .unwrap_or(0); 175 | self.is_covered_by_impl(prefixes, max_prefix_len) 176 | } 177 | 178 | fn is_covered_by_impl<'a, I>(&self, prefixes: I, max_prefix_len: usize) -> bool 179 | where 180 | I: IntoIterator + Clone, 181 | { 182 | prefixes 183 | .clone() 184 | .into_iter() 185 | .any(|x| x.is_compatible(self) && x.bit_count() <= self.bit_count()) 186 | || (self.bit_count() <= max_prefix_len 187 | && self 188 | .pushed(false) 189 | .is_covered_by_impl(prefixes.clone(), max_prefix_len) 190 | && self 191 | .pushed(true) 192 | .is_covered_by_impl(prefixes, max_prefix_len)) 193 | } 194 | 195 | /// Returns the neighbouring prefix differing in the `i`-th bit 196 | /// If `i` is larger than our bit count, `self` is returned 197 | pub fn with_flipped_bit(&self, i: u8) -> Self { 198 | if i as usize >= self.bit_count() { 199 | *self 200 | } else { 201 | Self::new(self.bit_count(), self.name.with_flipped_bit(i)) 202 | } 203 | } 204 | 205 | /// Returns the given `name` with first bits replaced by `self` 206 | pub fn substituted_in(&self, mut name: XorName) -> XorName { 207 | // TODO: is there a more efficient way of doing that? 208 | for i in 0..self.bit_count() { 209 | name = name.with_bit(i as u8, self.name.bit(i as u8)); 210 | } 211 | name 212 | } 213 | 214 | /// Returns the same prefix, with the last bit flipped, or unchanged, if empty. 215 | pub fn sibling(&self) -> Self { 216 | if self.bit_count() > 0 && self.bit_count() < 8 * XOR_NAME_LEN { 217 | self.with_flipped_bit(self.bit_count() as u8 - 1) 218 | } else { 219 | *self 220 | } 221 | } 222 | 223 | /// Returns the ancestors of this prefix that has the given bit count. 224 | /// 225 | /// # Panics 226 | /// 227 | /// Panics if `bit_count` is not less than the bit count of this prefix. 228 | pub fn ancestor(&self, bit_count: u8) -> Self { 229 | assert!((bit_count as usize) < self.bit_count()); 230 | Self::new(bit_count as usize, self.name) 231 | } 232 | 233 | /// Returns an iterator that yields all ancestors of this prefix. 234 | pub fn ancestors(&self) -> Ancestors { 235 | Ancestors { 236 | target: *self, 237 | current_len: 0, 238 | } 239 | } 240 | } 241 | 242 | impl PartialEq for Prefix { 243 | fn eq(&self, other: &Self) -> bool { 244 | self.is_compatible(other) && self.bit_count == other.bit_count 245 | } 246 | } 247 | 248 | impl PartialOrd for Prefix { 249 | fn partial_cmp(&self, other: &Self) -> Option { 250 | Some(self.cmp(other)) 251 | } 252 | } 253 | 254 | impl Ord for Prefix { 255 | fn cmp(&self, other: &Self) -> Ordering { 256 | if self == other { 257 | Ordering::Equal 258 | } else if self.is_compatible(other) { 259 | self.bit_count().cmp(&other.bit_count()) 260 | } else { 261 | self.name.cmp(&other.name) 262 | } 263 | } 264 | } 265 | 266 | impl Hash for Prefix { 267 | fn hash(&self, state: &mut H) { 268 | for i in 0..self.bit_count() { 269 | self.name.bit(i as u8).hash(state); 270 | } 271 | } 272 | } 273 | 274 | impl Binary for Prefix { 275 | fn fmt(&self, formatter: &mut Formatter) -> FmtResult { 276 | write!(formatter, "{0:1$b}", self.name, self.bit_count()) 277 | } 278 | } 279 | 280 | impl Debug for Prefix { 281 | fn fmt(&self, formatter: &mut Formatter) -> FmtResult { 282 | write!(formatter, "Prefix({:b})", self) 283 | } 284 | } 285 | 286 | /// Format `Prefix` as bit string, e.g. `"010"` with a [`Prefix::bit_count`] of `3`. 287 | impl Display for Prefix { 288 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 289 | // Use `Binary` impl from `XorName` with restricted width 290 | write!(f, "{:width$b}", self.name, width = self.bit_count as usize) 291 | } 292 | } 293 | 294 | #[derive(Debug)] 295 | pub enum FromStrError { 296 | InvalidChar(char), 297 | TooLong(usize), 298 | } 299 | 300 | impl Display for FromStrError { 301 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 302 | match self { 303 | FromStrError::InvalidChar(c) => { 304 | write!(f, "expected `0` or `1`, but encountered `{}`", c) 305 | } 306 | FromStrError::TooLong(l) => { 307 | write!( 308 | f, 309 | "max length exceeded {} with length of {l}", 310 | XOR_NAME_LEN * 8 311 | ) 312 | } 313 | } 314 | } 315 | } 316 | 317 | impl FromStr for Prefix { 318 | type Err = FromStrError; 319 | 320 | fn from_str(bits: &str) -> Result { 321 | if bits.len() > XOR_NAME_LEN * 8 { 322 | return Err(FromStrError::TooLong(bits.len())); 323 | } 324 | let mut name = [0; XOR_NAME_LEN]; 325 | for (i, bit) in bits.chars().enumerate() { 326 | if bit == '1' { 327 | let byte = i / 8; 328 | name[byte] |= 1 << (7 - (i % 8)); 329 | } else if bit != '0' { 330 | return Err(FromStrError::InvalidChar(bit)); 331 | } 332 | } 333 | Ok(Self::new(bits.len(), XorName(name))) 334 | } 335 | } 336 | 337 | /// Iterator that yields the ancestors of the given prefix starting at the root prefix. 338 | /// Does not include the prefix itself. 339 | pub struct Ancestors { 340 | target: Prefix, 341 | current_len: usize, 342 | } 343 | 344 | impl Iterator for Ancestors { 345 | type Item = Prefix; 346 | 347 | fn next(&mut self) -> Option { 348 | if self.current_len < self.target.bit_count() { 349 | let output = self.target.ancestor(self.current_len as u8); 350 | self.current_len += 1; 351 | Some(output) 352 | } else { 353 | None 354 | } 355 | } 356 | } 357 | 358 | #[cfg(test)] 359 | mod tests { 360 | use super::*; 361 | use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng}; 362 | 363 | #[test] 364 | fn prefix() { 365 | assert_eq!(parse("101").pushed(true), parse("1011")); 366 | assert_eq!(parse("101").pushed(false), parse("1010")); 367 | assert_eq!(parse("1011").popped(), parse("101")); 368 | assert!(parse("101").is_compatible(&parse("1010"))); 369 | assert!(parse("1010").is_compatible(&parse("101"))); 370 | assert!(!parse("1010").is_compatible(&parse("1011"))); 371 | assert!(parse("101").is_neighbour(&parse("1111"))); 372 | assert!(!parse("1010").is_neighbour(&parse("1111"))); 373 | assert!(parse("1010").is_neighbour(&parse("10111"))); 374 | assert!(!parse("101").is_neighbour(&parse("10111"))); 375 | assert!(parse("101").matches(&xor_name!(0b10101100))); 376 | assert!(!parse("1011").matches(&xor_name!(0b10101100))); 377 | 378 | assert_eq!(parse("0101").lower_bound(), xor_name!(0b01010000)); 379 | assert_eq!( 380 | parse("0101").upper_bound(), 381 | xor_name!( 382 | 0b01011111, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 383 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 384 | 255 385 | ) 386 | ); 387 | 388 | // Check we handle passing an excessive `bit_count` to `new()`. 389 | assert_eq!(Prefix::new(256, xor_name!(0)).bit_count(), 256); 390 | assert_eq!(Prefix::new(257, xor_name!(0)).bit_count(), 256); 391 | } 392 | 393 | #[test] 394 | fn breadth_first_order() { 395 | let expected = [ 396 | parse(""), 397 | parse("0"), 398 | parse("1"), 399 | parse("00"), 400 | parse("01"), 401 | parse("10"), 402 | parse("11"), 403 | parse("000"), 404 | parse("001"), 405 | parse("010"), 406 | parse("011"), 407 | parse("100"), 408 | parse("101"), 409 | parse("110"), 410 | parse("111"), 411 | ]; 412 | 413 | let mut rng = SmallRng::from_entropy(); 414 | 415 | for _ in 0..100 { 416 | let mut actual = expected; 417 | actual.shuffle(&mut rng); 418 | actual.sort_by(|lhs, rhs| lhs.cmp_breadth_first(rhs)); 419 | 420 | assert_eq!(actual, expected); 421 | } 422 | } 423 | 424 | #[test] 425 | fn ancestors() { 426 | let mut ancestors = parse("").ancestors(); 427 | assert_eq!(ancestors.next(), None); 428 | 429 | let mut ancestors = parse("0").ancestors(); 430 | assert_eq!(ancestors.next(), Some(parse(""))); 431 | assert_eq!(ancestors.next(), None); 432 | 433 | let mut ancestors = parse("01").ancestors(); 434 | assert_eq!(ancestors.next(), Some(parse(""))); 435 | assert_eq!(ancestors.next(), Some(parse("0"))); 436 | assert_eq!(ancestors.next(), None); 437 | 438 | let mut ancestors = parse("011").ancestors(); 439 | assert_eq!(ancestors.next(), Some(parse(""))); 440 | assert_eq!(ancestors.next(), Some(parse("0"))); 441 | assert_eq!(ancestors.next(), Some(parse("01"))); 442 | assert_eq!(ancestors.next(), None); 443 | } 444 | 445 | #[test] 446 | fn format_binary() { 447 | assert_eq!(&format!(0, "{:b}", parse("")), ""); 448 | assert_eq!(&format!(1, "{:b}", parse("0")), "0"); 449 | assert_eq!(&format!(2, "{:b}", parse("00")), "00"); 450 | assert_eq!(&format!(2, "{:b}", parse("01")), "01"); 451 | assert_eq!(&format!(2, "{:b}", parse("10")), "10"); 452 | assert_eq!(&format!(2, "{:b}", parse("11")), "11"); 453 | assert_eq!(&format!(7, "{:b}", parse("1100101")), "1100101"); 454 | 455 | // Bit string with 257 width 456 | assert!(Prefix::from_str(&"1".repeat(XOR_NAME_LEN * 8 + 1)).is_err()); 457 | } 458 | 459 | #[test] 460 | fn format_parse_roundtrip() { 461 | let format_parse_eq = |p| p == parse(&std::format!("{}", p)); 462 | 463 | assert!(format_parse_eq(Prefix::new(0, XorName([0xBB; 32])))); 464 | assert!(format_parse_eq(Prefix::new(256, XorName([0x33; 32])))); 465 | assert!(format_parse_eq(Prefix::new(5, XorName([0xAA; 32])))); 466 | assert!(format_parse_eq(Prefix::new(76, XorName([0xAA; 32])))); 467 | } 468 | 469 | fn parse(input: &str) -> Prefix { 470 | Prefix::from_str(input).unwrap() 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /src/serialize.rs: -------------------------------------------------------------------------------- 1 | use crate::{Prefix, XorName}; 2 | use serde::{ 3 | de::{self, Visitor}, 4 | ser::SerializeStruct, 5 | Deserialize, Deserializer, Serialize, Serializer, 6 | }; 7 | use std::{fmt, str::FromStr}; 8 | 9 | impl Serialize for XorName { 10 | fn serialize(&self, serializer: S) -> Result 11 | where 12 | S: Serializer, 13 | { 14 | // Return string with hexadecimal representation 15 | if serializer.is_human_readable() { 16 | return serializer.serialize_str(&hex::encode(self.0)); 17 | } 18 | 19 | // Default serialization. 20 | serializer.serialize_newtype_struct("XorName", &self.0) 21 | } 22 | } 23 | 24 | impl<'de> Deserialize<'de> for XorName { 25 | fn deserialize(deserializer: D) -> Result 26 | where 27 | D: Deserializer<'de>, 28 | { 29 | if deserializer.is_human_readable() { 30 | struct XorNameHexStrVisitor; 31 | impl<'de> Visitor<'de> for XorNameHexStrVisitor { 32 | type Value = XorName; 33 | 34 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 35 | write!(formatter, "32 byte hex string") 36 | } 37 | 38 | fn visit_str(self, s: &str) -> Result 39 | where 40 | E: de::Error, 41 | { 42 | let buffer = <[u8; 32] as hex::FromHex>::from_hex(s) 43 | .map_err(|e| E::custom(std::format!("hex decoding ({})", e)))?; 44 | Ok(XorName(buffer)) 45 | } 46 | } 47 | return deserializer.deserialize_str(XorNameHexStrVisitor); 48 | } 49 | 50 | #[derive(Deserialize)] 51 | #[serde(rename = "XorName")] 52 | struct XorNameDerived([u8; 32]); 53 | let x = ::deserialize(deserializer)?; 54 | Ok(XorName(x.0)) 55 | } 56 | } 57 | 58 | impl Serialize for Prefix { 59 | fn serialize(&self, serializer: S) -> Result 60 | where 61 | S: Serializer, 62 | { 63 | if serializer.is_human_readable() { 64 | // Use `Display` impl from `Prefix` 65 | return serializer.serialize_str(&std::format!("{}", self)); 66 | } 67 | 68 | let mut s = serializer.serialize_struct("Prefix", 2)?; 69 | s.serialize_field("bit_count", &self.bit_count)?; 70 | s.serialize_field("name", &self.name)?; 71 | s.end() 72 | } 73 | } 74 | impl<'de> Deserialize<'de> for Prefix { 75 | fn deserialize(deserializer: D) -> Result 76 | where 77 | D: Deserializer<'de>, 78 | { 79 | if deserializer.is_human_readable() { 80 | struct PrefixVisitor; 81 | impl<'de> Visitor<'de> for PrefixVisitor { 82 | type Value = Prefix; 83 | 84 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 85 | write!(formatter, "binary formatted string") 86 | } 87 | 88 | fn visit_str(self, s: &str) -> Result 89 | where 90 | E: de::Error, 91 | { 92 | Prefix::from_str(s).map_err(|e| { 93 | E::custom(std::format!("could not convert string to `Prefix`: {e}")) 94 | }) 95 | } 96 | } 97 | return deserializer.deserialize_str(PrefixVisitor); 98 | } 99 | 100 | #[derive(Deserialize)] 101 | #[serde(rename = "Prefix")] 102 | struct PrefixDerived { 103 | bit_count: u16, 104 | name: XorName, 105 | } 106 | let p = ::deserialize(deserializer)?; 107 | Ok(Prefix { 108 | bit_count: p.bit_count, 109 | name: p.name, 110 | }) 111 | } 112 | } 113 | 114 | #[cfg(test)] 115 | mod test { 116 | use super::*; 117 | use serde_test::*; 118 | 119 | /// `XorName` with derived `Serialize` impl. Used to compare against. 120 | #[derive(PartialEq, Debug, serde::Serialize, Deserialize)] 121 | struct XorNameDerived([u8; 32]); 122 | 123 | /// `Prefix` with derived `Serialize` impl. Used to compare against. 124 | #[derive(PartialEq, Debug, serde::Serialize, Deserialize)] 125 | struct PrefixDerived { 126 | bit_count: u16, 127 | name: XorNameDerived, 128 | } 129 | 130 | #[test] 131 | fn xorname_ser_de() { 132 | let xor = XorName([0xAA; 32]); 133 | let xor_derived = XorNameDerived([0xAA; 32]); 134 | 135 | let xor_hex_str = static_str("aa".repeat(32)); 136 | assert_tokens(&xor.readable(), &[Token::Str(xor_hex_str)]); 137 | 138 | assert_tokens(&xor.compact(), &xor_tokens("XorName")); 139 | // Verify our `Serialize` impl is same as when it would be derived 140 | assert_tokens(&xor_derived.compact(), &xor_tokens("XorNameDerived")); 141 | } 142 | 143 | #[test] 144 | fn prefix_ser_de() { 145 | let bit_count = 15; 146 | let prefix = Prefix { 147 | bit_count, 148 | name: XorName([0xAA; 32]), 149 | }; 150 | let prefix_derived = PrefixDerived { 151 | bit_count, 152 | name: XorNameDerived([0xAA; 32]), 153 | }; 154 | 155 | assert_tokens(&prefix.readable(), &[Token::Str("101010101010101")]); 156 | 157 | assert_tokens( 158 | &prefix.compact(), 159 | &prefix_tokens(bit_count, "Prefix", "XorName"), 160 | ); 161 | // Verify our `Serialize` impl is same as when it would be derived 162 | assert_tokens( 163 | &prefix_derived.compact(), 164 | &prefix_tokens(bit_count, "PrefixDerived", "XorNameDerived"), 165 | ); 166 | } 167 | 168 | // Little helper to leak a &str to obtain a static str (`Token::Str` requires &'static str) 169 | fn static_str(s: String) -> &'static str { 170 | Box::leak(s.into_boxed_str()) 171 | } 172 | 173 | // Compact/derived representation of `XorName` 174 | fn xor_tokens(name: &'static str) -> Vec { 175 | let mut a = vec![]; 176 | a.extend_from_slice(&[Token::NewtypeStruct { name }, Token::Tuple { len: 32 }]); 177 | a.extend_from_slice(&[Token::U8(0xAA); 32]); // Repeat a U8 Token 32 times 178 | a.extend_from_slice(&[Token::TupleEnd]); 179 | a 180 | } 181 | 182 | // Compact/derived representation of `Prefix` 183 | fn prefix_tokens(bit_count: u16, name: &'static str, name2: &'static str) -> Vec { 184 | let mut v = vec![ 185 | Token::Struct { name, len: 2 }, 186 | Token::Str("bit_count"), 187 | Token::U16(bit_count), 188 | Token::Str("name"), 189 | ]; 190 | v.extend_from_slice(&xor_tokens(name2)); 191 | v.extend_from_slice(&[Token::StructEnd]); 192 | v 193 | } 194 | } 195 | --------------------------------------------------------------------------------